Commit fc8eac548a698174554892f6bba8ca9758cf34f9

Authored by YunaiV
1 parent ee9a358b

集成 Spring Security 组件,重构后,整体逻辑更加清晰

Showing 27 changed files with 1821 additions and 177 deletions
http-client.env.json
1 { 1 {
2 "local": { 2 "local": {
3 "baseUrl": "http://127.0.0.1:8080/api", 3 "baseUrl": "http://127.0.0.1:8080/api",
4 - "token": "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MDk2ODE2MzEsInN1YiI6ImE3ZGE1MWE2YWUyYTQxOWRhNmExYTlkYmJiMTVmZjc4In0.RXG7alSz64lE9oPSgbnYT_KsX7kvoHVhF5oHxXHztr1KjsttOqOppSmHGBYFI7Y75bsjEBSxSqbGsS1O1S2b1w" 4 + "token": "yudaoyuanma1"
5 } 5 }
6 } 6 }
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java renamed to src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityConfiguration.java
1 -package com.ruoyi.framework.config;  
2 -  
3 -import org.springframework.beans.factory.annotation.Autowired;  
4 -import org.springframework.context.annotation.Bean;  
5 -import org.springframework.http.HttpMethod;  
6 -import org.springframework.security.authentication.AuthenticationManager;  
7 -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
8 -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;  
9 -import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
10 -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
11 -import org.springframework.security.config.http.SessionCreationPolicy;  
12 -import org.springframework.security.core.userdetails.UserDetailsService;  
13 -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;  
14 -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;  
15 -import org.springframework.security.web.authentication.logout.LogoutFilter;  
16 -import org.springframework.web.filter.CorsFilter;  
17 -import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;  
18 -import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;  
19 -import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;  
20 -  
21 -/**  
22 - * spring security配置  
23 - *  
24 - * @author ruoyi  
25 - */  
26 -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)  
27 -public class SecurityConfig extends WebSecurityConfigurerAdapter  
28 -{  
29 - /**  
30 - * 自定义用户认证逻辑  
31 - */  
32 - @Autowired  
33 - private UserDetailsService userDetailsService;  
34 -  
35 - /**  
36 - * 认证失败处理类  
37 - */  
38 - @Autowired  
39 - private AuthenticationEntryPointImpl unauthorizedHandler;  
40 -  
41 - /**  
42 - * 退出处理类  
43 - */  
44 - @Autowired  
45 - private LogoutSuccessHandlerImpl logoutSuccessHandler;  
46 -  
47 - /**  
48 - * token认证过滤器  
49 - */  
50 - @Autowired  
51 - private JwtAuthenticationTokenFilter authenticationTokenFilter;  
52 -  
53 - /**  
54 - * 跨域过滤器  
55 - */  
56 - @Autowired  
57 - private CorsFilter corsFilter;  
58 -  
59 - /**  
60 - * 解决 无法直接注入 AuthenticationManager  
61 - *  
62 - * @return  
63 - * @throws Exception  
64 - */  
65 - @Bean  
66 - @Override  
67 - public AuthenticationManager authenticationManagerBean() throws Exception  
68 - {  
69 - return super.authenticationManagerBean();  
70 - }  
71 -  
72 - /**  
73 - * anyRequest | 匹配所有请求路径  
74 - * access | SpringEl表达式结果为true时可以访问  
75 - * anonymous | 匿名可以访问  
76 - * denyAll | 用户不能访问  
77 - * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)  
78 - * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问  
79 - * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问  
80 - * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问  
81 - * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问  
82 - * hasRole | 如果有参数,参数表示角色,则其角色可以访问  
83 - * permitAll | 用户可以任意访问  
84 - * rememberMe | 允许通过remember-me登录的用户访问  
85 - * authenticated | 用户登录后可访问  
86 - */  
87 - @Override  
88 - protected void configure(HttpSecurity httpSecurity) throws Exception  
89 - {  
90 - httpSecurity  
91 - // CSRF禁用,因为不使用session  
92 - .csrf().disable()  
93 - // 认证失败处理类  
94 - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()  
95 - // 基于token,所以不需要session  
96 - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()  
97 - // 过滤请求  
98 - .authorizeRequests()  
99 - // 对于登录login 验证码captchaImage 允许匿名访问  
100 - .antMatchers("/login", "/captchaImage").anonymous()  
101 - .antMatchers(  
102 - HttpMethod.GET,  
103 - "/*.html",  
104 - "/**/*.html",  
105 - "/**/*.css",  
106 - "/**/*.js"  
107 - ).permitAll()  
108 - .antMatchers("/profile/**").anonymous()  
109 - .antMatchers("/common/download**").anonymous()  
110 - .antMatchers("/common/download/resource**").anonymous()  
111 - .antMatchers("/swagger-ui.html").anonymous()  
112 - .antMatchers("/swagger-resources/**").anonymous()  
113 - .antMatchers("/webjars/**").anonymous()  
114 - .antMatchers("/*/api-docs").anonymous()  
115 - .antMatchers("/druid/**").anonymous()  
116 - // 除上面外的所有请求全部需要鉴权认证  
117 - .anyRequest().authenticated()  
118 - .and()  
119 - .headers().frameOptions().disable();  
120 - httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);  
121 - // 添加JWT filter  
122 - httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  
123 - // 添加CORS filter  
124 - httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);  
125 - httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);  
126 - }  
127 -  
128 -  
129 - /**  
130 - * 强散列哈希加密实现  
131 - */  
132 - @Bean  
133 - public BCryptPasswordEncoder bCryptPasswordEncoder()  
134 - {  
135 - return new BCryptPasswordEncoder();  
136 - }  
137 -  
138 - /**  
139 - * 身份认证接口  
140 - */  
141 - @Override  
142 - protected void configure(AuthenticationManagerBuilder auth) throws Exception  
143 - {  
144 - auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());  
145 - }  
146 -} 1 +package cn.iocoder.dashboard.framework.security.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.core.filter.JwtAuthenticationTokenFilter;
  4 +import cn.iocoder.dashboard.framework.security.core.handler.AuthenticationEntryPointImpl;
  5 +import cn.iocoder.dashboard.framework.security.core.handler.LogoutSuccessHandlerImpl;
  6 +import cn.iocoder.dashboard.framework.web.config.WebProperties;
  7 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  8 +import org.springframework.context.annotation.Bean;
  9 +import org.springframework.http.HttpMethod;
  10 +import org.springframework.security.authentication.AuthenticationManager;
  11 +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  12 +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  13 +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  14 +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  15 +import org.springframework.security.config.http.SessionCreationPolicy;
  16 +import org.springframework.security.core.userdetails.UserDetailsService;
  17 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  18 +import org.springframework.security.crypto.password.PasswordEncoder;
  19 +import org.springframework.security.web.AuthenticationEntryPoint;
  20 +import org.springframework.security.web.access.AccessDeniedHandler;
  21 +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  22 +import org.springframework.security.web.authentication.logout.LogoutFilter;
  23 +import org.springframework.web.filter.CorsFilter;
  24 +
  25 +import javax.annotation.Resource;
  26 +
  27 +/**
  28 + * spring security配置
  29 + *
  30 + * @author ruoyi
  31 + */
  32 +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
  33 +@EnableConfigurationProperties(SecurityProperties.class)
  34 +public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  35 +
  36 + /**
  37 + * 自定义用户认证逻辑
  38 + */
  39 + @Resource
  40 + private UserDetailsService userDetailsService;
  41 +
  42 + /**
  43 + * 认证失败处理类
  44 + */
  45 + @Resource
  46 + private AuthenticationEntryPoint unauthorizedHandler;
  47 + /**
  48 + * 权限不够处理器
  49 + */
  50 + @Resource
  51 + private AccessDeniedHandler accessDeniedHandler;
  52 + /**
  53 + * 退出处理类
  54 + */
  55 + @Resource
  56 + private LogoutSuccessHandlerImpl logoutSuccessHandler;
  57 +
  58 + /**
  59 + * Token 认证过滤器
  60 + */
  61 + @Resource
  62 + private JwtAuthenticationTokenFilter authenticationTokenFilter;
  63 +
  64 + @Resource
  65 + private WebProperties webProperties;
  66 +
  67 + /**
  68 + * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入
  69 + * 通过覆写父类的该方法,添加 @Bean 注解,解决该问题
  70 + */
  71 + @Bean
  72 + @Override
  73 + public AuthenticationManager authenticationManagerBean() throws Exception {
  74 + return super.authenticationManagerBean();
  75 + }
  76 +
  77 + /**
  78 + * Spring Security 加密器
  79 + * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
  80 + *
  81 + * @see <a href="http://stackabuse.com/password-encoding-with-spring-security/">Password Encoding with Spring Security</a>
  82 + */
  83 + @Bean
  84 + public PasswordEncoder passwordEncoder() {
  85 + return new BCryptPasswordEncoder();
  86 + }
  87 +
  88 + /**
  89 + * 身份认证接口
  90 + */
  91 + @Override
  92 + protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  93 + auth.userDetailsService(userDetailsService)
  94 + .passwordEncoder(passwordEncoder());
  95 + }
  96 +
  97 + /**
  98 + * 配置 URL 的安全配置
  99 + *
  100 + * anyRequest | 匹配所有请求路径
  101 + * access | SpringEl表达式结果为true时可以访问
  102 + * anonymous | 匿名可以访问
  103 + * denyAll | 用户不能访问
  104 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
  105 + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
  106 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
  107 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
  108 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
  109 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问
  110 + * permitAll | 用户可以任意访问
  111 + * rememberMe | 允许通过remember-me登录的用户访问
  112 + * authenticated | 用户登录后可访问
  113 + */
  114 + @Override
  115 + protected void configure(HttpSecurity httpSecurity) throws Exception {
  116 + httpSecurity
  117 + // CSRF 禁用,因为不使用 Session
  118 + .csrf().disable()
  119 + // 基于 token 机制,所以不需要 Session
  120 + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
  121 + // 一堆自定义的 Spring Security 处理器
  122 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
  123 + .accessDeniedHandler(accessDeniedHandler).and()
  124 + // TODO 过滤请求
  125 + .authorizeRequests()
  126 + // 登陆的接口,可匿名访问
  127 + .antMatchers(webProperties.getApiPrefix() + "/login").anonymous()
  128 + // 通用的接口,可匿名访问
  129 + .antMatchers( webProperties.getApiPrefix() + "/captcha/**").anonymous()
  130 + // TODO
  131 + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
  132 + .antMatchers("/profile/**").anonymous()
  133 + .antMatchers("/common/download**").anonymous()
  134 + .antMatchers("/common/download/resource**").anonymous()
  135 + .antMatchers("/swagger-ui.html").anonymous()
  136 + .antMatchers("/swagger-resources/**").anonymous()
  137 + .antMatchers("/webjars/**").anonymous()
  138 + .antMatchers("/*/api-docs").anonymous()
  139 + .antMatchers("/druid/**").hasAnyAuthority("druid") // TODO 芋艿,未来需要在拓展下
  140 + // 除上面外的所有请求全部需要鉴权认证
  141 + .anyRequest().authenticated()
  142 + .and()
  143 + .headers().frameOptions().disable();
  144 + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
  145 + // 添加 JWT Filter
  146 + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  147 + }
  148 +
  149 +}
src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityProperties.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import javax.validation.Valid;
  8 +import javax.validation.constraints.NotEmpty;
  9 +import javax.validation.constraints.NotNull;
  10 +import java.time.Duration;
  11 +
  12 +@ConfigurationProperties(prefix = "yudao.security")
  13 +@Validated
  14 +@Data
  15 +public class SecurityProperties {
  16 +
  17 + /**
  18 + * HTTP 请求时,访问令牌的请求 Header
  19 + */
  20 + @NotEmpty(message = "Token Header 不能为空")
  21 + private String tokenHeader;
  22 + /**
  23 + * Token 过期时间
  24 + */
  25 + @NotNull(message = "Token 过期时间不能为空")
  26 + private Duration tokenTimeout;
  27 + /**
  28 + * Token 秘钥
  29 + */
  30 + @NotEmpty(message = "Token 秘钥不能为空")
  31 + private String tokenSecret;
  32 + /**
  33 + * Session 过期时间
  34 + *
  35 + * 当 User 用户超过当前时间未操作,则 Session 会过期
  36 + */
  37 + @NotNull(message = "Session 过期时间不能为空")
  38 + private Duration sessionTimeout;
  39 +
  40 + /**
  41 + * mock 模式的开关
  42 + */
  43 + @NotNull(message = "mock 模式的开关不能为空")
  44 + private Boolean mockEnable;
  45 + /**
  46 + * mock 模式的秘钥
  47 + * 一定要配置秘钥,保证安全性
  48 + */
  49 + @NotEmpty(message = "mock 模式的秘钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
  50 + private String mockSecret = "yudaoyuanma";
  51 +
  52 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/LoginUser.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.enums.user.UserStatus;
  4 +import com.alibaba.fastjson.annotation.JSONField;
  5 +import lombok.Data;
  6 +import org.springframework.data.annotation.Transient;
  7 +import org.springframework.security.core.GrantedAuthority;
  8 +import org.springframework.security.core.userdetails.UserDetails;
  9 +
  10 +import java.util.Collection;
  11 +import java.util.Date;
  12 +import java.util.Set;
  13 +
  14 +/**
  15 + * 登陆用户信息
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Data
  20 +public class LoginUser implements UserDetails {
  21 +
  22 + /**
  23 + * 用户编号
  24 + */
  25 + private Long userId;
  26 + /**
  27 + * 角色编号数组
  28 + */
  29 + private Set<Integer> roleIds;
  30 + /**
  31 + * 最后更新时间
  32 + */
  33 + private Date updateTime;
  34 +
  35 + /**
  36 + * 用户名
  37 + */
  38 + private String username;
  39 + /**
  40 + * 密码
  41 + */
  42 + private String password;
  43 + /**
  44 + * 状态
  45 + */
  46 + private String status;
  47 +
  48 + @Override
  49 + @JSONField(serialize = false) // 避免序列化
  50 + public String getPassword() {
  51 + return password;
  52 + }
  53 +
  54 + @Override
  55 + @JSONField(serialize = false) // 避免序列化
  56 + public String getUsername() {
  57 + return username;
  58 + }
  59 +
  60 + @Override
  61 + @JSONField(serialize = false) // 避免序列化
  62 + public boolean isEnabled() {
  63 + return UserStatus.OK.getCode().equals(status);
  64 + }
  65 +
  66 + @Override
  67 + @JSONField(serialize = false) // 避免序列化
  68 + public Collection<? extends GrantedAuthority> getAuthorities() {
  69 + return null;
  70 + }
  71 +
  72 + @Override
  73 + @JSONField(serialize = false) // 避免序列化
  74 + public boolean isAccountNonExpired() {
  75 + return true; // 返回 true,不依赖 Spring Security 判断
  76 + }
  77 +
  78 + @Override
  79 + @JSONField(serialize = false) // 避免序列化
  80 + public boolean isAccountNonLocked() {
  81 + return true; // 返回 true,不依赖 Spring Security 判断
  82 + }
  83 +
  84 + @Override
  85 + @JSONField(serialize = false) // 避免序列化
  86 + public boolean isCredentialsNonExpired() {
  87 + return true; // 返回 true,不依赖 Spring Security 判断
  88 + }
  89 +
  90 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.filter;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.extra.servlet.ServletUtil;
  5 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  6 +import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
  7 +import cn.iocoder.dashboard.framework.security.core.LoginUser;
  8 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  9 +import cn.iocoder.dashboard.framework.web.core.handler.GlobalExceptionHandler;
  10 +import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
  11 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  12 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  13 +import org.springframework.security.core.context.SecurityContextHolder;
  14 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  15 +import org.springframework.stereotype.Component;
  16 +import org.springframework.web.filter.OncePerRequestFilter;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.servlet.FilterChain;
  20 +import javax.servlet.ServletException;
  21 +import javax.servlet.http.HttpServletRequest;
  22 +import javax.servlet.http.HttpServletResponse;
  23 +import java.io.IOException;
  24 +
  25 +/**
  26 + * JWT 过滤器,验证 token 的有效性
  27 + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
  28 + *
  29 + * @author ruoyi
  30 + */
  31 +@Component
  32 +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  33 +
  34 + @Resource
  35 + private SecurityProperties securityProperties;
  36 + @Resource
  37 + private SysAuthService authService;
  38 + @Resource
  39 + private GlobalExceptionHandler globalExceptionHandler;
  40 +
  41 + @Override
  42 + @SuppressWarnings("NullableProblems")
  43 + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
  44 + throws ServletException, IOException {
  45 + String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
  46 + if (StrUtil.isNotEmpty(token)) {
  47 + try {
  48 + // 验证 token 有效性
  49 + LoginUser loginUser = authService.verifyTokenAndRefresh(token);
  50 + // 模拟 Login 功能,方便日常开发调试
  51 + if (loginUser == null) {
  52 + loginUser = this.mockLoginUser(token);
  53 + }
  54 + // 设置当前用户
  55 + if (loginUser != null) {
  56 + SecurityUtils.setLoginUser(loginUser, request);
  57 + }
  58 + } catch (Throwable ex) {
  59 + CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
  60 + ServletUtils.writeJSON(response, result);
  61 + return;
  62 + }
  63 + }
  64 +
  65 + // 继续过滤链
  66 + chain.doFilter(request, response);
  67 + }
  68 +
  69 + /**
  70 + * 模拟登陆用户,方便日常开发调试
  71 + *
  72 + * 注意,在线上环境下,一定要关闭该功能!!!
  73 + *
  74 + * @param token 模拟的 token,格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号
  75 + * @return 模拟的 LoginUser
  76 + */
  77 + private LoginUser mockLoginUser(String token) {
  78 + if (!securityProperties.getMockEnable()) {
  79 + return null;
  80 + }
  81 + // 必须以 mockSecret 开头
  82 + if (!token.startsWith(securityProperties.getMockSecret())) {
  83 + return null;
  84 + }
  85 + Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
  86 + return authService.mockLogin(userId);
  87 + }
  88 +
  89 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.handler;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  4 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  6 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.security.access.AccessDeniedException;
  9 +import org.springframework.security.core.AuthenticationException;
  10 +import org.springframework.security.web.access.AccessDeniedHandler;
  11 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import javax.servlet.FilterChain;
  15 +import javax.servlet.ServletException;
  16 +import javax.servlet.http.HttpServletRequest;
  17 +import javax.servlet.http.HttpServletResponse;
  18 +import java.io.IOException;
  19 +
  20 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
  21 +
  22 +/**
  23 + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
  24 + *
  25 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Component
  30 +@Slf4j
  31 +@SuppressWarnings("JavadocReference")
  32 +public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  33 +
  34 + @Override
  35 + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
  36 + throws IOException, ServletException {
  37 + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
  38 + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
  39 + SecurityUtils.getLoginUser().getUserId(), e);
  40 + // 返回 403
  41 + ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED));
  42 + }
  43 +
  44 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AuthenticationEntryPointImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.handler;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  4 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.security.core.AuthenticationException;
  8 +import org.springframework.security.web.AuthenticationEntryPoint;
  9 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.servlet.FilterChain;
  13 +import javax.servlet.http.HttpServletRequest;
  14 +import javax.servlet.http.HttpServletResponse;
  15 +
  16 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
  17 +
  18 +/**
  19 + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
  20 + *
  21 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
  22 + *
  23 + * @author ruoyi
  24 + */
  25 +@Component
  26 +@Slf4j
  27 +@SuppressWarnings("JavadocReference") // 忽略文档引用报错
  28 +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  29 +
  30 + @Override
  31 + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
  32 + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
  33 + // 返回 401
  34 + ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED));
  35 + }
  36 +
  37 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.handler;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
  5 +import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService;
  6 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  7 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  8 +import org.springframework.security.core.Authentication;
  9 +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.annotation.Resource;
  13 +import javax.servlet.http.HttpServletRequest;
  14 +import javax.servlet.http.HttpServletResponse;
  15 +
  16 +
  17 +/**
  18 + * 自定义退出处理器
  19 + *
  20 + * @author ruoyi
  21 + */
  22 +@Component
  23 +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
  24 +
  25 + @Resource
  26 + private SecurityProperties securityProperties;
  27 +
  28 + @Resource
  29 + private SecurityFrameworkService securityFrameworkService;
  30 +
  31 + @Override
  32 + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
  33 + // 执行退出
  34 + String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
  35 + if (StrUtil.isNotBlank(token)) {
  36 + securityFrameworkService.logout(token);
  37 + }
  38 + // 返回成功
  39 + ServletUtils.writeJSON(response, null);
  40 +// ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.OK.value(), "退出成功")));
  41 + }
  42 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityFrameworkService.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.service;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import org.springframework.security.core.userdetails.UserDetailsService;
  5 +
  6 +/**
  7 + * Security 框架 Service 接口,定义 security 组件需要的功能
  8 + */
  9 +public interface SecurityFrameworkService extends UserDetailsService {
  10 +
  11 + /**
  12 + * 基于 token 退出登录
  13 + *
  14 + * @param token token
  15 + */
  16 + void logout(String token);
  17 +
  18 + /**
  19 + * 校验 token 的有效性,并获取用户信息
  20 + * 通过后,刷新 token 的过期时间
  21 + *
  22 + * @param token token
  23 + * @return 用户信息
  24 + */
  25 + LoginUser verifyTokenAndRefresh(String token);
  26 +
  27 + /**
  28 + * 模拟指定用户编号的 LoginUser
  29 + *
  30 + * @param userId 用户编号
  31 + * @return 登录用户
  32 + */
  33 + LoginUser mockLogin(Long userId);
  34 +
  35 +}
src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.util;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  5 +import org.springframework.security.core.context.SecurityContextHolder;
  6 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  7 +import org.springframework.util.StringUtils;
  8 +
  9 +import javax.servlet.http.HttpServletRequest;
  10 +
  11 +/**
  12 + * 安全服务工具类
  13 + *
  14 + * @author ruoyi
  15 + */
  16 +public class SecurityUtils {
  17 +
  18 + /**
  19 + * 从请求中,获得认证 Token
  20 + *
  21 + * @param request 请求
  22 + * @param header 认证 Token 对应的 Header 名字
  23 + * @return 认证 Token
  24 + */
  25 + public static String obtainAuthorization(HttpServletRequest request, String header) {
  26 + String authorization = request.getHeader(header);
  27 + if (!StringUtils.hasText(authorization)) {
  28 + return null;
  29 + }
  30 + int index = authorization.indexOf("Bearer ");
  31 + if (index == -1) { // 未找到
  32 + return null;
  33 + }
  34 + return authorization.substring(index + 7).trim();
  35 + }
  36 +
  37 + /**
  38 + * 获取当前用户
  39 + */
  40 + public static LoginUser getLoginUser() {
  41 + return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  42 + }
  43 +
  44 + /**
  45 + * 设置当前用户
  46 + *
  47 + * @param loginUser 登陆用户
  48 + * @param request 请求
  49 + */
  50 + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
  51 + // 创建 UsernamePasswordAuthenticationToken 对象
  52 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
  53 + loginUser, null, null);
  54 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  55 + // 设置到上下文
  56 + SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  57 + }
  58 +
  59 +}
src/main/java/cn/iocoder/dashboard/framework/security/package-info.java 0 → 100644
  1 +/**
  2 + * 基于 Spring Security 框架
  3 + * 实现安全认证功能
  4 + *
  5 + * @author 芋道源码
  6 + */
  7 +package cn.iocoder.dashboard.framework.security;
src/main/java/cn/iocoder/dashboard/framework/security/《芋道 Spring Boot 安全框架 Spring Security 入门》.md 0 → 100644
  1 +* 芋道 Spring Security 入门:<http://www.iocoder.cn/Spring-Boot/Spring-Security/?dashboard>
  2 +* Spring Security 基本概念:<http://www.iocoder.cn/Fight/Spring-Security-4-1-0-Basic-concept-description/?dashboard>
src/main/java/cn/iocoder/dashboard/modules/system/convert/auth/SysAuthConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.convert.auth;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  5 +import org.mapstruct.Mapper;
  6 +import org.mapstruct.Mapping;
  7 +import org.mapstruct.factory.Mappers;
  8 +
  9 +@Mapper
  10 +public interface SysAuthConvert {
  11 +
  12 + SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class);
  13 +
  14 + @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略
  15 + LoginUser convert(SysUserDO bean);
  16 +
  17 +}
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dao;
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/user/SysUserMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dao.user;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  4 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +@Mapper
  9 +public interface SysUserMapper extends BaseMapper<SysUserDO> {
  10 +
  11 + default SysUserDO selectByUsername(String username) {
  12 + return selectOne(new QueryWrapper<SysUserDO>().eq("username", username));
  13 + }
  14 +
  15 +}
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/user/SysUserDO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user;
  2 +
  3 +import java.util.Date;
  4 +import java.util.List;
  5 +import javax.validation.constraints.Email;
  6 +import javax.validation.constraints.NotBlank;
  7 +import javax.validation.constraints.Size;
  8 +
  9 +import cn.iocoder.dashboard.framework.excel.Excel;
  10 +import cn.iocoder.dashboard.framework.excel.Excels;
  11 +import cn.iocoder.dashboard.framework.mybatis.core.BaseDO;
  12 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDept;
  13 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRole;
  14 +import com.baomidou.mybatisplus.annotation.TableField;
  15 +import com.baomidou.mybatisplus.annotation.TableId;
  16 +import com.baomidou.mybatisplus.annotation.TableName;
  17 +import com.fasterxml.jackson.annotation.JsonIgnore;
  18 +import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  22 +
  23 +/**
  24 + * 用户 DO
  25 + *
  26 + * @author ruoyi
  27 + */
  28 +@TableName("sys_user")
  29 +@Data
  30 +@EqualsAndHashCode(callSuper = true)
  31 +// TODO 芋艿:数据库的字段默认值
  32 +public class SysUserDO extends BaseDO {
  33 +
  34 + /**
  35 + * 用户ID
  36 + */
  37 + @TableId
  38 + @Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号")
  39 + private Long userId;
  40 +
  41 + /**
  42 + * 部门ID
  43 + */
  44 + @Excel(name = "部门编号", type = Excel.Type.IMPORT)
  45 + private Long deptId;
  46 +
  47 + /**
  48 + * 用户账号
  49 + */
  50 + @Excel(name = "登录名称")
  51 + @NotBlank(message = "用户账号不能为空")
  52 + @Size(max = 30, message = "用户账号长度不能超过30个字符")
  53 + private String username;
  54 +
  55 + /**
  56 + * 加密后的密码
  57 + *
  58 + * 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐
  59 + */
  60 + private String password;
  61 +
  62 + /**
  63 + * 用户昵称
  64 + */
  65 + @Excel(name = "用户名称")
  66 + @Size(max = 30, message = "用户昵称长度不能超过30个字符")
  67 + private String nickname;
  68 +
  69 + /**
  70 + * 用户邮箱
  71 + */
  72 + @Excel(name = "用户邮箱")
  73 + @Email(message = "邮箱格式不正确")
  74 + @Size(max = 50, message = "邮箱长度不能超过50个字符")
  75 + private String email;
  76 +
  77 + /**
  78 + * 手机号码
  79 + */
  80 + @Excel(name = "手机号码")
  81 + @Size(max = 11, message = "手机号码长度不能超过11个字符")
  82 + private String mobile;
  83 +
  84 + /**
  85 + * 用户性别
  86 + */
  87 + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
  88 + private String sex;
  89 +
  90 + /**
  91 + * 用户头像
  92 + */
  93 + private String avatar;
  94 +
  95 + /**
  96 + * 帐号状态(0正常 1停用)
  97 + */
  98 + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
  99 + // TODO 芋艿:修改成枚举
  100 + private String status;
  101 +
  102 + /**
  103 + * 最后登录IP
  104 + */
  105 + @Excel(name = "最后登录IP", type = Excel.Type.EXPORT)
  106 + private String loginIp;
  107 +
  108 + /**
  109 + * 最后登录时间
  110 + */
  111 + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT)
  112 + private Date loginDate;
  113 +
  114 +
  115 + // TODO FROM 芋艿:下面的字段,需要忽略
  116 +
  117 + /**
  118 + * 部门对象
  119 + */
  120 + @Excels({
  121 + @Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT),
  122 + @Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT)
  123 + })
  124 + @TableField(exist = false)
  125 + private SysDept dept;
  126 +
  127 + /**
  128 + * 角色对象
  129 + */
  130 + @TableField(exist = false)
  131 + private List<SysRole> roles;
  132 +
  133 + /**
  134 + * 角色组
  135 + */
  136 + @TableField(exist = false)
  137 + private Long[] roleIds;
  138 +
  139 + /**
  140 + * 岗位组
  141 + */
  142 + @TableField(exist = false)
  143 + private Long[] postIds;
  144 +
  145 + // TODO 芋艿:后续清理掉
  146 + public boolean isAdmin() {
  147 + return isAdmin(this.userId);
  148 + }
  149 +
  150 + public static boolean isAdmin(Long userId) {
  151 + return userId != null && 1L == userId;
  152 + }
  153 +
  154 +}
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.enums;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +
  5 +/**
  6 + * 错误码枚举类
  7 + *
  8 + * system 系统,使用 1-002-000-000 段
  9 + */
  10 +public interface SysErrorCodeConstants {
  11 +
  12 + // ========== AUTH 模块 1002000000 ==========
  13 + ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
  14 + ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
  15 + ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底,位置原因
  16 +
  17 + // ========== TOKEN 模块 1002001000 ==========
  18 + ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");
  19 + ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败");
  20 +
  21 +}
ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/enums/user/UserStatus.java
1 -package com.ruoyi.common.enums;  
2 -  
3 -/**  
4 - * 用户状态  
5 - *  
6 - * @author ruoyi  
7 - */  
8 -public enum UserStatus  
9 -{  
10 - OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");  
11 -  
12 - private final String code;  
13 - private final String info;  
14 -  
15 - UserStatus(String code, String info)  
16 - {  
17 - this.code = code;  
18 - this.info = info;  
19 - }  
20 -  
21 - public String getCode()  
22 - {  
23 - return code;  
24 - }  
25 -  
26 - public String getInfo()  
27 - {  
28 - return info;  
29 - }  
30 -} 1 +package cn.iocoder.dashboard.modules.system.enums.user;
  2 +
  3 +/**
  4 + * 用户状态
  5 + *
  6 + * @author ruoyi
  7 + */
  8 +public enum UserStatus
  9 +{
  10 + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
  11 +
  12 + private final String code;
  13 + private final String info;
  14 +
  15 + UserStatus(String code, String info)
  16 + {
  17 + this.code = code;
  18 + this.info = info;
  19 + }
  20 +
  21 + public String getCode()
  22 + {
  23 + return code;
  24 + }
  25 +
  26 + public String getInfo()
  27 + {
  28 + return info;
  29 + }
  30 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.auth;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService;
  4 +
  5 +/**
  6 + * 认证 Service 接口
  7 + *
  8 + * 提供用户的账号密码登陆、token 的校验等认证相关的功能
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface SysAuthService extends SecurityFrameworkService {
  13 +
  14 + String login(String username, String password, String captchaUUID, String captchaCode);
  15 +
  16 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysTokenService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.auth;
  2 +
  3 +import io.jsonwebtoken.Claims;
  4 +
  5 +import java.util.Map;
  6 +
  7 +/**
  8 + * Token Service 接口
  9 + *
  10 + * 提供访问 Token 令牌,目前基于 JWT 实现
  11 + */
  12 +public interface SysTokenService {
  13 +
  14 + /**
  15 + * 创建 Token
  16 + *
  17 + * @param subject 主体
  18 + * @return Token 字符串
  19 + */
  20 + String createToken(String subject);
  21 +
  22 + /**
  23 + * 解析 Token,返回 claims 数据声明
  24 + *
  25 + * @param token Token
  26 + * @return claims
  27 + */
  28 + Claims parseToken(String token);
  29 +
  30 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.auth.impl;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
  6 +import cn.iocoder.dashboard.framework.security.core.LoginUser;
  7 +import cn.iocoder.dashboard.modules.system.enums.user.UserStatus;
  8 +import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert;
  9 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  10 +import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO;
  11 +import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
  12 +import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService;
  13 +import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
  14 +import cn.iocoder.dashboard.util.date.DateUtils;
  15 +import io.jsonwebtoken.Claims;
  16 +import io.jsonwebtoken.JwtException;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.security.authentication.AuthenticationManager;
  19 +import org.springframework.security.authentication.BadCredentialsException;
  20 +import org.springframework.security.authentication.DisabledException;
  21 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  22 +import org.springframework.security.core.Authentication;
  23 +import org.springframework.security.core.AuthenticationException;
  24 +import org.springframework.security.core.userdetails.UserDetails;
  25 +import org.springframework.security.core.userdetails.UsernameNotFoundException;
  26 +import org.springframework.stereotype.Service;
  27 +import org.springframework.util.Assert;
  28 +
  29 +import javax.annotation.Resource;
  30 +import java.util.Collections;
  31 +import java.util.Date;
  32 +import java.util.Set;
  33 +
  34 +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
  35 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  36 +
  37 +/**
  38 + * Auth Service 实现类
  39 + *
  40 + * @author 芋道源码
  41 + */
  42 +@Service
  43 +@Slf4j
  44 +public class SysAuthServiceImpl implements SysAuthService {
  45 +
  46 + @Resource
  47 + private SecurityProperties securityProperties;
  48 +
  49 + @Resource
  50 + private SysTokenService tokenService;
  51 + @Resource
  52 + private AuthenticationManager authenticationManager;
  53 + @Resource
  54 + private SysUserService userService;
  55 + @Resource
  56 + private SysLoginUserRedisDAO loginUserRedisDAO;
  57 +
  58 + @Override
  59 + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  60 + // 获取 username 对应的 SysUserDO
  61 + SysUserDO user = userService.getUserByUserName(username);
  62 + if (user == null) {
  63 + throw new UsernameNotFoundException(username);
  64 + }
  65 +
  66 + // 创建 LoginUser 对象
  67 + return SysAuthConvert.INSTANCE.convert(user);
  68 + }
  69 +
  70 + @Override
  71 + public LoginUser mockLogin(Long userId) {
  72 + // 获取用户编号对应的 SysUserDO
  73 + SysUserDO user = userService.getUser(userId);
  74 + if (user == null) {
  75 + throw new UsernameNotFoundException(String.valueOf(userId));
  76 + }
  77 +
  78 + // 创建 LoginUser 对象
  79 + LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
  80 + loginUser.setUpdateTime(new Date());
  81 + loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId()));
  82 + return loginUser;
  83 + }
  84 +
  85 + @Override
  86 + public String login(String username, String password, String captchaUUID, String captchaCode) {
  87 + // 判断验证码是否正确
  88 + this.verifyCaptcha(captchaUUID, captchaCode);
  89 +
  90 + // 使用账号密码,进行登陆。
  91 + LoginUser loginUser = this.login0(username, password);
  92 +
  93 + // 缓存登陆用户到 Redis 中
  94 + String sessionId = IdUtil.fastSimpleUUID();
  95 + loginUser.setUpdateTime(new Date());
  96 + loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId()));
  97 + loginUserRedisDAO.set(sessionId, loginUser);
  98 +
  99 + // 创建 Token
  100 + // 我们在返回给前端的 JWT 中,使用 sessionId 作为 subject 主体,标识当前 User 用户
  101 + return tokenService.createToken(sessionId);
  102 + }
  103 +
  104 + private void verifyCaptcha(String captchaUUID, String captchaCode) {
  105 + // String verifyKey = Constants.CAPTCHA_CODE_KEY + captchaUUID;
  106 +// String captcha = redisCache.getCacheObject(verifyKey);
  107 +// redisCache.deleteObject(verifyKey);
  108 +// if (captcha == null) {
  109 +// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
  110 +// throw new CaptchaExpireException();
  111 +// }
  112 +// if (!code.equalsIgnoreCase(captcha)) {
  113 +// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
  114 +// throw new CaptchaException();
  115 +// }
  116 + }
  117 +
  118 + private LoginUser login0(String username, String password) {
  119 + // 用户验证
  120 + Authentication authentication;
  121 + try {
  122 + // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
  123 + // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
  124 + authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
  125 + } catch (BadCredentialsException badCredentialsException) {
  126 + // TODO 日志优化
  127 +// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
  128 + throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
  129 + } catch (DisabledException disabledException) {
  130 + // TODO 日志优化
  131 + throw exception(AUTH_LOGIN_USER_DISABLED);
  132 + } catch (AuthenticationException authenticationException) {
  133 + // TODO 日志优化
  134 + throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
  135 + }
  136 + // TODO 需要优化
  137 +// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  138 + Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
  139 + return (LoginUser) authentication.getPrincipal();
  140 + }
  141 +
  142 + /**
  143 + * 获得 User 拥有的角色编号数组
  144 + *
  145 + * @param userId 用户编号
  146 + * @return 角色编号数组
  147 + */
  148 + private Set<Integer> getUserRoleIds(Long userId) {
  149 + // TODO 芋艿:读取角色编号
  150 + return Collections.emptySet();
  151 + }
  152 +
  153 + @Override
  154 + public void logout(String token) {
  155 +// AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); TODO 需要搞一搞
  156 + }
  157 +
  158 + @Override
  159 + public LoginUser verifyTokenAndRefresh(String token) {
  160 + // 验证 token 的有效性
  161 + String sessionId = this.verifyToken(token);
  162 + // 获得 LoginUser
  163 + LoginUser loginUser = loginUserRedisDAO.get(sessionId);
  164 + if (loginUser == null) {
  165 +// throw exception(AUTH_SESSION_TIMEOUT);
  166 + return null;
  167 + }
  168 + // 刷新 LoginUser 缓存
  169 + this.refreshLoginUserCache(sessionId, loginUser);
  170 + return loginUser;
  171 + }
  172 +
  173 + private String verifyToken(String token) {
  174 + Claims claims;
  175 + try {
  176 + claims = tokenService.parseToken(token);
  177 + } catch (JwtException jwtException) {
  178 + log.warn("[verifyToken][token({}) 解析发生异常]", token);
  179 +// throw exception(TOKEN_PARSE_FAIL);
  180 + return null;
  181 + }
  182 + // token 已经过期
  183 + if (DateUtils.isExpired(claims.getExpiration())) {
  184 +// throw exception(TOKEN_EXPIRED);
  185 + return null;
  186 + }
  187 + // 判断 sessionId 是否存在
  188 + String sessionId = claims.getSubject();
  189 + if (StrUtil.isBlank(sessionId)) {
  190 +// throw exception(AUTH_SESSION_ID_NOT_FOUND);
  191 + return null;
  192 + }
  193 + return sessionId;
  194 + }
  195 +
  196 + private void refreshLoginUserCache(String sessionId, LoginUser loginUser) {
  197 + // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
  198 + if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
  199 + securityProperties.getSessionTimeout().toMillis() / 3) {
  200 + return;
  201 + }
  202 +
  203 + // 重新加载 SysUserDO 信息
  204 + SysUserDO user = userService.getUser(loginUser.getUserId());
  205 + if (user == null || UserStatus.DISABLE.getCode().equals(user.getStatus())) {
  206 + throw exception(TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登陆界面
  207 + }
  208 +
  209 + // 刷新 LoginUser 缓存
  210 + loginUser.setUpdateTime(new Date());
  211 + loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId()));
  212 + loginUserRedisDAO.set(sessionId, loginUser);
  213 + }
  214 +
  215 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysTokenServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.auth.impl;
  2 +
  3 +import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
  4 +import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService;
  5 +import cn.iocoder.dashboard.util.date.DateUtils;
  6 +import io.jsonwebtoken.Claims;
  7 +import io.jsonwebtoken.Jwts;
  8 +import io.jsonwebtoken.SignatureAlgorithm;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +import javax.annotation.Resource;
  12 +import java.util.HashMap;
  13 +import java.util.Map;
  14 +
  15 +/**
  16 + * Token Service 实现类
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Service
  21 +public class SysTokenServiceImpl implements SysTokenService {
  22 +
  23 + @Resource
  24 + private SecurityProperties securityProperties;
  25 +
  26 + @Override
  27 + public String createToken(String subject) {
  28 + return Jwts.builder()
  29 + .signWith(SignatureAlgorithm.HS512, securityProperties.getTokenSecret())
  30 + .setExpiration(DateUtils.addTime(securityProperties.getTokenTimeout()))
  31 + .setSubject(subject)
  32 + .compact();
  33 + }
  34 +
  35 + @Override
  36 + public Claims parseToken(String token) {
  37 + return Jwts.parser()
  38 + .setSigningKey(securityProperties.getTokenSecret())
  39 + .parseClaimsJws(token)
  40 + .getBody();
  41 + }
  42 +
  43 + public static void main(String[] args) {
  44 + String secret = "abcdefghijklmnopqrstuvwxyz";
  45 + Map<String, Object> map = new HashMap<>();
  46 + map.put("key1", "value1");
  47 + System.out.println(Jwts.builder()
  48 + .signWith(SignatureAlgorithm.HS512, secret)
  49 + .setClaims(map)
  50 + .compact());
  51 +
  52 + System.out.println(Jwts.parser()
  53 + .setSigningKey(secret)
  54 + .parseClaimsJws("qyJhbGciOiJIUzUxMiJ9.eyJrZXkxIjoidmFsdWUxIn0.AHWncLRBlJkqrKaoWHZmMgbqYIT7rfLs8KCp9LuC0mdNfnx1xEMm1N9bgcD-0lc5sjySqsKiWzqJ3rpoyUSh0g")
  55 + .getBody());
  56 + }
  57 +
  58 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.user;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  4 +
  5 +/**
  6 + * 用户 Service 接口
  7 + *
  8 + * @author ruoyi
  9 + */
  10 +public interface SysUserService {
  11 +// /**
  12 +// * 根据条件分页查询用户列表
  13 +// *
  14 +// * @param user 用户信息
  15 +// * @return 用户信息集合信息
  16 +// */
  17 +// public List<SysUser> selectUserList(SysUser user);
  18 +
  19 + /**
  20 + * 通过用户名查询用户
  21 + *
  22 + * @param username 用户名
  23 + * @return 用户对象信息
  24 + */
  25 + SysUserDO getUserByUserName(String username);
  26 +
  27 + /**
  28 + * 通过用户 ID 查询用户
  29 + *
  30 + * @param userId 用户ID
  31 + * @return 用户对象信息
  32 + */
  33 + SysUserDO getUser(Long userId);
  34 +
  35 +//
  36 +// /**
  37 +// * 根据用户ID查询用户所属角色组
  38 +// *
  39 +// * @param userName 用户名
  40 +// * @return 结果
  41 +// */
  42 +// public String selectUserRoleGroup(String userName);
  43 +//
  44 +// /**
  45 +// * 根据用户ID查询用户所属岗位组
  46 +// *
  47 +// * @param userName 用户名
  48 +// * @return 结果
  49 +// */
  50 +// public String selectUserPostGroup(String userName);
  51 +//
  52 +// /**
  53 +// * 校验用户名称是否唯一
  54 +// *
  55 +// * @param userName 用户名称
  56 +// * @return 结果
  57 +// */
  58 +// public String checkUserNameUnique(String userName);
  59 +//
  60 +// /**
  61 +// * 校验手机号码是否唯一
  62 +// *
  63 +// * @param user 用户信息
  64 +// * @return 结果
  65 +// */
  66 +// public String checkPhoneUnique(SysUser user);
  67 +//
  68 +// /**
  69 +// * 校验email是否唯一
  70 +// *
  71 +// * @param user 用户信息
  72 +// * @return 结果
  73 +// */
  74 +// public String checkEmailUnique(SysUser user);
  75 +//
  76 +// /**
  77 +// * 校验用户是否允许操作
  78 +// *
  79 +// * @param user 用户信息
  80 +// */
  81 +// public void checkUserAllowed(SysUser user);
  82 +//
  83 +// /**
  84 +// * 新增用户信息
  85 +// *
  86 +// * @param user 用户信息
  87 +// * @return 结果
  88 +// */
  89 +// public int insertUser(SysUser user);
  90 +//
  91 +// /**
  92 +// * 修改用户信息
  93 +// *
  94 +// * @param user 用户信息
  95 +// * @return 结果
  96 +// */
  97 +// public int updateUser(SysUser user);
  98 +//
  99 +// /**
  100 +// * 修改用户状态
  101 +// *
  102 +// * @param user 用户信息
  103 +// * @return 结果
  104 +// */
  105 +// public int updateUserStatus(SysUser user);
  106 +//
  107 +// /**
  108 +// * 修改用户基本信息
  109 +// *
  110 +// * @param user 用户信息
  111 +// * @return 结果
  112 +// */
  113 +// public int updateUserProfile(SysUser user);
  114 +//
  115 +// /**
  116 +// * 修改用户头像
  117 +// *
  118 +// * @param userName 用户名
  119 +// * @param avatar 头像地址
  120 +// * @return 结果
  121 +// */
  122 +// public boolean updateUserAvatar(String userName, String avatar);
  123 +//
  124 +// /**
  125 +// * 重置用户密码
  126 +// *
  127 +// * @param user 用户信息
  128 +// * @return 结果
  129 +// */
  130 +// public int resetPwd(SysUser user);
  131 +//
  132 +// /**
  133 +// * 重置用户密码
  134 +// *
  135 +// * @param userName 用户名
  136 +// * @param password 密码
  137 +// * @return 结果
  138 +// */
  139 +// public int resetUserPwd(String userName, String password);
  140 +//
  141 +// /**
  142 +// * 通过用户ID删除用户
  143 +// *
  144 +// * @param userId 用户ID
  145 +// * @return 结果
  146 +// */
  147 +// public int deleteUserById(Long userId);
  148 +//
  149 +// /**
  150 +// * 批量删除用户信息
  151 +// *
  152 +// * @param userIds 需要删除的用户ID
  153 +// * @return 结果
  154 +// */
  155 +// public int deleteUserByIds(Long[] userIds);
  156 +//
  157 +// /**
  158 +// * 导入用户数据
  159 +// *
  160 +// * @param userList 用户数据列表
  161 +// * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
  162 +// * @param operName 操作用户
  163 +// * @return 结果
  164 +// */
  165 +// public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
  166 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.user;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.user.SysUserMapper;
  4 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.stereotype.Service;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +
  11 +/**
  12 + * 用户 业务层处理
  13 + *
  14 + * @author ruoyi
  15 + */
  16 +@Service
  17 +@Slf4j
  18 +public class SysUserServiceImpl implements SysUserService {
  19 +
  20 + @Resource
  21 + private SysUserMapper userMapper;
  22 +
  23 +// @Autowired
  24 +// private SysUserMapper userMapper;
  25 +//
  26 +// @Autowired
  27 +// private SysRoleMapper roleMapper;
  28 +//
  29 +// @Autowired
  30 +// private SysPostMapper postMapper;
  31 +//
  32 +// @Autowired
  33 +// private SysUserRoleMapper userRoleMapper;
  34 +//
  35 +// @Autowired
  36 +// private SysUserPostMapper userPostMapper;
  37 +//
  38 +// @Autowired
  39 +// private ISysConfigService configService;
  40 +
  41 +// /**
  42 +// * 根据条件分页查询用户列表
  43 +// *
  44 +// * @param user 用户信息
  45 +// * @return 用户信息集合信息
  46 +// */
  47 +// @Override
  48 +// @DataScope(deptAlias = "d", userAlias = "u")
  49 +// public List<SysUser> selectUserList(SysUser user)
  50 +// {
  51 +// return userMapper.selectUserList(user);
  52 +// }
  53 +
  54 + @Override
  55 + public SysUserDO getUserByUserName(String username) {
  56 + return userMapper.selectByUsername(username);
  57 + }
  58 +
  59 + @Override
  60 + public SysUserDO getUser(Long userId) {
  61 + return userMapper.selectById(userId);
  62 + }
  63 +
  64 +// /**
  65 +// * 通过用户ID查询用户
  66 +// *
  67 +// * @param userId 用户ID
  68 +// * @return 用户对象信息
  69 +// */
  70 +// @Override
  71 +// public SysUser selectUserById(Long userId)
  72 +// {
  73 +// return userMapper.selectUserById(userId);
  74 +// }
  75 +//
  76 +// /**
  77 +// * 查询用户所属角色组
  78 +// *
  79 +// * @param userName 用户名
  80 +// * @return 结果
  81 +// */
  82 +// @Override
  83 +// public String selectUserRoleGroup(String userName)
  84 +// {
  85 +// List<SysRole> list = roleMapper.selectRolesByUserName(userName);
  86 +// StringBuffer idsStr = new StringBuffer();
  87 +// for (SysRole role : list)
  88 +// {
  89 +// idsStr.append(role.getRoleName()).append(",");
  90 +// }
  91 +// if (StringUtils.isNotEmpty(idsStr.toString()))
  92 +// {
  93 +// return idsStr.substring(0, idsStr.length() - 1);
  94 +// }
  95 +// return idsStr.toString();
  96 +// }
  97 +//
  98 +// /**
  99 +// * 查询用户所属岗位组
  100 +// *
  101 +// * @param userName 用户名
  102 +// * @return 结果
  103 +// */
  104 +// @Override
  105 +// public String selectUserPostGroup(String userName)
  106 +// {
  107 +// List<SysPost> list = postMapper.selectPostsByUserName(userName);
  108 +// StringBuffer idsStr = new StringBuffer();
  109 +// for (SysPost post : list)
  110 +// {
  111 +// idsStr.append(post.getPostName()).append(",");
  112 +// }
  113 +// if (StringUtils.isNotEmpty(idsStr.toString()))
  114 +// {
  115 +// return idsStr.substring(0, idsStr.length() - 1);
  116 +// }
  117 +// return idsStr.toString();
  118 +// }
  119 +//
  120 +// /**
  121 +// * 校验用户名称是否唯一
  122 +// *
  123 +// * @param userName 用户名称
  124 +// * @return 结果
  125 +// */
  126 +// @Override
  127 +// public String checkUserNameUnique(String userName)
  128 +// {
  129 +// int count = userMapper.checkUserNameUnique(userName);
  130 +// if (count > 0)
  131 +// {
  132 +// return UserConstants.NOT_UNIQUE;
  133 +// }
  134 +// return UserConstants.UNIQUE;
  135 +// }
  136 +//
  137 +// /**
  138 +// * 校验用户名称是否唯一
  139 +// *
  140 +// * @param user 用户信息
  141 +// * @return
  142 +// */
  143 +// @Override
  144 +// public String checkPhoneUnique(SysUser user)
  145 +// {
  146 +// Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
  147 +// SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber());
  148 +// if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
  149 +// {
  150 +// return UserConstants.NOT_UNIQUE;
  151 +// }
  152 +// return UserConstants.UNIQUE;
  153 +// }
  154 +//
  155 +// /**
  156 +// * 校验email是否唯一
  157 +// *
  158 +// * @param user 用户信息
  159 +// * @return
  160 +// */
  161 +// @Override
  162 +// public String checkEmailUnique(SysUser user)
  163 +// {
  164 +// Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
  165 +// SysUser info = userMapper.checkEmailUnique(user.getEmail());
  166 +// if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
  167 +// {
  168 +// return UserConstants.NOT_UNIQUE;
  169 +// }
  170 +// return UserConstants.UNIQUE;
  171 +// }
  172 +//
  173 +// /**
  174 +// * 校验用户是否允许操作
  175 +// *
  176 +// * @param user 用户信息
  177 +// */
  178 +// @Override
  179 +// public void checkUserAllowed(SysUser user)
  180 +// {
  181 +// if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin())
  182 +// {
  183 +// throw new CustomException("不允许操作超级管理员用户");
  184 +// }
  185 +// }
  186 +//
  187 +// /**
  188 +// * 新增保存用户信息
  189 +// *
  190 +// * @param user 用户信息
  191 +// * @return 结果
  192 +// */
  193 +// @Override
  194 +// @Transactional
  195 +// public int insertUser(SysUser user)
  196 +// {
  197 +// // 新增用户信息
  198 +// int rows = userMapper.insertUser(user);
  199 +// // 新增用户岗位关联
  200 +// insertUserPost(user);
  201 +// // 新增用户与角色管理
  202 +// insertUserRole(user);
  203 +// return rows;
  204 +// }
  205 +//
  206 +// /**
  207 +// * 修改保存用户信息
  208 +// *
  209 +// * @param user 用户信息
  210 +// * @return 结果
  211 +// */
  212 +// @Override
  213 +// @Transactional
  214 +// public int updateUser(SysUser user)
  215 +// {
  216 +// Long userId = user.getUserId();
  217 +// // 删除用户与角色关联
  218 +// userRoleMapper.deleteUserRoleByUserId(userId);
  219 +// // 新增用户与角色管理
  220 +// insertUserRole(user);
  221 +// // 删除用户与岗位关联
  222 +// userPostMapper.deleteUserPostByUserId(userId);
  223 +// // 新增用户与岗位管理
  224 +// insertUserPost(user);
  225 +// return userMapper.updateUser(user);
  226 +// }
  227 +//
  228 +// /**
  229 +// * 修改用户状态
  230 +// *
  231 +// * @param user 用户信息
  232 +// * @return 结果
  233 +// */
  234 +// @Override
  235 +// public int updateUserStatus(SysUser user)
  236 +// {
  237 +// return userMapper.updateUser(user);
  238 +// }
  239 +//
  240 +// /**
  241 +// * 修改用户基本信息
  242 +// *
  243 +// * @param user 用户信息
  244 +// * @return 结果
  245 +// */
  246 +// @Override
  247 +// public int updateUserProfile(SysUser user)
  248 +// {
  249 +// return userMapper.updateUser(user);
  250 +// }
  251 +//
  252 +// /**
  253 +// * 修改用户头像
  254 +// *
  255 +// * @param userName 用户名
  256 +// * @param avatar 头像地址
  257 +// * @return 结果
  258 +// */
  259 +// @Override
  260 +// public boolean updateUserAvatar(String userName, String avatar)
  261 +// {
  262 +// return userMapper.updateUserAvatar(userName, avatar) > 0;
  263 +// }
  264 +//
  265 +// /**
  266 +// * 重置用户密码
  267 +// *
  268 +// * @param user 用户信息
  269 +// * @return 结果
  270 +// */
  271 +// @Override
  272 +// public int resetPwd(SysUser user)
  273 +// {
  274 +// return userMapper.updateUser(user);
  275 +// }
  276 +//
  277 +// /**
  278 +// * 重置用户密码
  279 +// *
  280 +// * @param userName 用户名
  281 +// * @param password 密码
  282 +// * @return 结果
  283 +// */
  284 +// @Override
  285 +// public int resetUserPwd(String userName, String password)
  286 +// {
  287 +// return userMapper.resetUserPwd(userName, password);
  288 +// }
  289 +//
  290 +// /**
  291 +// * 新增用户角色信息
  292 +// *
  293 +// * @param user 用户对象
  294 +// */
  295 +// public void insertUserRole(SysUser user)
  296 +// {
  297 +// Long[] roles = user.getRoleIds();
  298 +// if (StringUtils.isNotNull(roles))
  299 +// {
  300 +// // 新增用户与角色管理
  301 +// List<SysUserRole> list = new ArrayList<SysUserRole>();
  302 +// for (Long roleId : roles)
  303 +// {
  304 +// SysUserRole ur = new SysUserRole();
  305 +// ur.setUserId(user.getUserId());
  306 +// ur.setRoleId(roleId);
  307 +// list.add(ur);
  308 +// }
  309 +// if (list.size() > 0)
  310 +// {
  311 +// userRoleMapper.batchUserRole(list);
  312 +// }
  313 +// }
  314 +// }
  315 +//
  316 +// /**
  317 +// * 新增用户岗位信息
  318 +// *
  319 +// * @param user 用户对象
  320 +// */
  321 +// public void insertUserPost(SysUser user)
  322 +// {
  323 +// Long[] posts = user.getPostIds();
  324 +// if (StringUtils.isNotNull(posts))
  325 +// {
  326 +// // 新增用户与岗位管理
  327 +// List<SysUserPost> list = new ArrayList<SysUserPost>();
  328 +// for (Long postId : posts)
  329 +// {
  330 +// SysUserPost up = new SysUserPost();
  331 +// up.setUserId(user.getUserId());
  332 +// up.setPostId(postId);
  333 +// list.add(up);
  334 +// }
  335 +// if (list.size() > 0)
  336 +// {
  337 +// userPostMapper.batchUserPost(list);
  338 +// }
  339 +// }
  340 +// }
  341 +//
  342 +// /**
  343 +// * 通过用户ID删除用户
  344 +// *
  345 +// * @param userId 用户ID
  346 +// * @return 结果
  347 +// */
  348 +// @Override
  349 +// @Transactional
  350 +// public int deleteUserById(Long userId)
  351 +// {
  352 +// // 删除用户与角色关联
  353 +// userRoleMapper.deleteUserRoleByUserId(userId);
  354 +// // 删除用户与岗位表
  355 +// userPostMapper.deleteUserPostByUserId(userId);
  356 +// return userMapper.deleteUserById(userId);
  357 +// }
  358 +//
  359 +// /**
  360 +// * 批量删除用户信息
  361 +// *
  362 +// * @param userIds 需要删除的用户ID
  363 +// * @return 结果
  364 +// */
  365 +// @Override
  366 +// @Transactional
  367 +// public int deleteUserByIds(Long[] userIds)
  368 +// {
  369 +// for (Long userId : userIds)
  370 +// {
  371 +// checkUserAllowed(new SysUser(userId));
  372 +// }
  373 +// // 删除用户与角色关联
  374 +// userRoleMapper.deleteUserRole(userIds);
  375 +// // 删除用户与岗位关联
  376 +// userPostMapper.deleteUserPost(userIds);
  377 +// return userMapper.deleteUserByIds(userIds);
  378 +// }
  379 +//
  380 +// /**
  381 +// * 导入用户数据
  382 +// *
  383 +// * @param userList 用户数据列表
  384 +// * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
  385 +// * @param operName 操作用户
  386 +// * @return 结果
  387 +// */
  388 +// @Override
  389 +// public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName)
  390 +// {
  391 +// if (StringUtils.isNull(userList) || userList.size() == 0)
  392 +// {
  393 +// throw new CustomException("导入用户数据不能为空!");
  394 +// }
  395 +// int successNum = 0;
  396 +// int failureNum = 0;
  397 +// StringBuilder successMsg = new StringBuilder();
  398 +// StringBuilder failureMsg = new StringBuilder();
  399 +// String password = configService.selectConfigByKey("sys.user.initPassword");
  400 +// for (SysUser user : userList)
  401 +// {
  402 +// try
  403 +// {
  404 +// // 验证是否存在这个用户
  405 +// SysUser u = userMapper.selectUserByUserName(user.getUserName());
  406 +// if (StringUtils.isNull(u))
  407 +// {
  408 +// user.setPassword(SecurityUtils.encryptPassword(password));
  409 +// user.setCreateBy(operName);
  410 +// this.insertUser(user);
  411 +// successNum++;
  412 +// successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
  413 +// }
  414 +// else if (isUpdateSupport)
  415 +// {
  416 +// user.setUpdateBy(operName);
  417 +// this.updateUser(user);
  418 +// successNum++;
  419 +// successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
  420 +// }
  421 +// else
  422 +// {
  423 +// failureNum++;
  424 +// failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
  425 +// }
  426 +// }
  427 +// catch (Exception e)
  428 +// {
  429 +// failureNum++;
  430 +// String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
  431 +// failureMsg.append(msg + e.getMessage());
  432 +// log.error(msg, e);
  433 +// }
  434 +// }
  435 +// if (failureNum > 0)
  436 +// {
  437 +// failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
  438 +// throw new CustomException(failureMsg.toString());
  439 +// }
  440 +// else
  441 +// {
  442 +// successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
  443 +// }
  444 +// return successMsg.toString();
  445 +// }
  446 +
  447 +}
src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.util.date;
  2 +
  3 +import java.time.Duration;
  4 +import java.util.Date;
  5 +
  6 +/**
  7 + * 时间工具类
  8 + */
  9 +public class DateUtils {
  10 +
  11 + public static Date addTime(Duration duration) {
  12 + return new Date(System.currentTimeMillis() + duration.toMillis());
  13 + }
  14 +
  15 + public static boolean isExpired(Date time) {
  16 + return System.currentTimeMillis() > time.getTime();
  17 + }
  18 +
  19 +}
src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.util.servlet;
  2 +
  3 +import cn.hutool.extra.servlet.ServletUtil;
  4 +import com.alibaba.fastjson.JSON;
  5 +import org.springframework.http.MediaType;
  6 +
  7 +
  8 +import javax.servlet.http.HttpServletResponse;
  9 +
  10 +/**
  11 + * 客户端工具类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class ServletUtils {
  16 +
  17 + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
  18 + public static void writeJSON(HttpServletResponse response, Object object) {
  19 + String content = JSON.toJSONString(object);
  20 + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
  21 + }
  22 +
  23 +}
src/main/resources/application.yaml
@@ -27,6 +27,8 @@ yudao: @@ -27,6 +27,8 @@ yudao:
27 token-secret: abcdefghijklmnopqrstuvwxyz 27 token-secret: abcdefghijklmnopqrstuvwxyz
28 token-timeout: 1d 28 token-timeout: 1d
29 session-timeout: 30m 29 session-timeout: 30m
  30 + mock-enable: true
  31 + mock-secret: yudaoyuanma
30 swagger: 32 swagger:
31 title: 管理后台 33 title: 管理后台
32 description: 提供管理员管理的所有功能 34 description: 提供管理员管理的所有功能