mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 02:08:43 +08:00 
			
		
		
		
	集成 Spring Security 组件,重构后,整体逻辑更加清晰
This commit is contained in:
		| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "local": { | ||||
|     "baseUrl": "http://127.0.0.1:8080/api", | ||||
|     "token": "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MDk2ODE2MzEsInN1YiI6ImE3ZGE1MWE2YWUyYTQxOWRhNmExYTlkYmJiMTVmZjc4In0.RXG7alSz64lE9oPSgbnYT_KsX7kvoHVhF5oHxXHztr1KjsttOqOppSmHGBYFI7Y75bsjEBSxSqbGsS1O1S2b1w" | ||||
|     "token": "yudaoyuanma1" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| package com.ruoyi.framework.config; | ||||
| package cn.iocoder.dashboard.framework.security.config; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import cn.iocoder.dashboard.framework.security.core.filter.JwtAuthenticationTokenFilter; | ||||
| import cn.iocoder.dashboard.framework.security.core.handler.AuthenticationEntryPointImpl; | ||||
| import cn.iocoder.dashboard.framework.security.core.handler.LogoutSuccessHandlerImpl; | ||||
| import cn.iocoder.dashboard.framework.web.config.WebProperties; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.security.authentication.AuthenticationManager; | ||||
| @@ -11,12 +15,14 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur | ||||
| import org.springframework.security.config.http.SessionCreationPolicy; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||||
| import org.springframework.security.web.authentication.logout.LogoutFilter; | ||||
| import org.springframework.web.filter.CorsFilter; | ||||
| import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; | ||||
| import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; | ||||
| import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; | ||||
| 
 | ||||
| import javax.annotation.Resource; | ||||
| 
 | ||||
| /** | ||||
|  * spring security配置 | ||||
| @@ -24,52 +30,73 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | ||||
| public class SecurityConfig extends WebSecurityConfigurerAdapter | ||||
| { | ||||
| @EnableConfigurationProperties(SecurityProperties.class) | ||||
| public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | ||||
| 
 | ||||
|     /** | ||||
|      * 自定义用户认证逻辑 | ||||
|      */ | ||||
|     @Autowired | ||||
|     @Resource | ||||
|     private UserDetailsService userDetailsService; | ||||
| 
 | ||||
|     /** | ||||
|      * 认证失败处理类 | ||||
|      */ | ||||
|     @Autowired | ||||
|     private AuthenticationEntryPointImpl unauthorizedHandler; | ||||
| 
 | ||||
|     @Resource | ||||
|     private AuthenticationEntryPoint unauthorizedHandler; | ||||
|     /** | ||||
|      * 权限不够处理器 | ||||
|      */ | ||||
|     @Resource | ||||
|     private AccessDeniedHandler accessDeniedHandler; | ||||
|     /** | ||||
|      * 退出处理类 | ||||
|      */ | ||||
|     @Autowired | ||||
|     @Resource | ||||
|     private LogoutSuccessHandlerImpl logoutSuccessHandler; | ||||
| 
 | ||||
|     /** | ||||
|      * token认证过滤器 | ||||
|      * Token 认证过滤器 | ||||
|      */ | ||||
|     @Autowired | ||||
|     @Resource | ||||
|     private JwtAuthenticationTokenFilter authenticationTokenFilter; | ||||
| 
 | ||||
|     /** | ||||
|      * 跨域过滤器 | ||||
|      */ | ||||
|     @Autowired | ||||
|     private CorsFilter corsFilter; | ||||
|     @Resource | ||||
|     private WebProperties webProperties; | ||||
| 
 | ||||
|     /** | ||||
|      * 解决 无法直接注入 AuthenticationManager | ||||
|      * | ||||
|      * @return | ||||
|      * @throws Exception | ||||
|      * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入 | ||||
|      * 通过覆写父类的该方法,添加 @Bean 注解,解决该问题 | ||||
|      */ | ||||
|     @Bean | ||||
|     @Override | ||||
|     public AuthenticationManager authenticationManagerBean() throws Exception | ||||
|     { | ||||
|     public AuthenticationManager authenticationManagerBean() throws Exception { | ||||
|         return super.authenticationManagerBean(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Spring Security 加密器 | ||||
|      * 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器 | ||||
|      * | ||||
|      * @see <a href="http://stackabuse.com/password-encoding-with-spring-security/">Password Encoding with Spring Security</a> | ||||
|      */ | ||||
|     @Bean | ||||
|     public PasswordEncoder passwordEncoder() { | ||||
|         return new BCryptPasswordEncoder(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 身份认证接口 | ||||
|      */ | ||||
|     @Override | ||||
|     protected void configure(AuthenticationManagerBuilder auth) throws Exception { | ||||
|         auth.userDetailsService(userDetailsService) | ||||
|                 .passwordEncoder(passwordEncoder()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 配置 URL 的安全配置 | ||||
|      * | ||||
|      * anyRequest          |   匹配所有请求路径 | ||||
|      * access              |   SpringEl表达式结果为true时可以访问 | ||||
|      * anonymous           |   匿名可以访问 | ||||
| @@ -85,26 +112,23 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter | ||||
|      * authenticated       |   用户登录后可访问 | ||||
|      */ | ||||
|     @Override | ||||
|     protected void configure(HttpSecurity httpSecurity) throws Exception | ||||
|     { | ||||
|     protected void configure(HttpSecurity httpSecurity) throws Exception { | ||||
|         httpSecurity | ||||
|                 // CSRF禁用,因为不使用session | ||||
|                 // CSRF 禁用,因为不使用 Session | ||||
|                 .csrf().disable() | ||||
|                 // 认证失败处理类 | ||||
|                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() | ||||
|                 // 基于token,所以不需要session | ||||
|                 // 基于 token 机制,所以不需要 Session | ||||
|                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() | ||||
|                 // 过滤请求 | ||||
|                 // 一堆自定义的 Spring Security 处理器 | ||||
|                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) | ||||
|                     .accessDeniedHandler(accessDeniedHandler).and() | ||||
|                 // TODO 过滤请求 | ||||
|                 .authorizeRequests() | ||||
|                 // 对于登录login 验证码captchaImage 允许匿名访问 | ||||
|                 .antMatchers("/login", "/captchaImage").anonymous() | ||||
|                 .antMatchers( | ||||
|                         HttpMethod.GET, | ||||
|                         "/*.html", | ||||
|                         "/**/*.html", | ||||
|                         "/**/*.css", | ||||
|                         "/**/*.js" | ||||
|                 ).permitAll() | ||||
|                 // 登陆的接口,可匿名访问 | ||||
|                 .antMatchers(webProperties.getApiPrefix() + "/login").anonymous() | ||||
|                 // 通用的接口,可匿名访问 | ||||
|                 .antMatchers( webProperties.getApiPrefix() + "/captcha/**").anonymous() | ||||
|                 // TODO | ||||
|                 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | ||||
|                 .antMatchers("/profile/**").anonymous() | ||||
|                 .antMatchers("/common/download**").anonymous() | ||||
|                 .antMatchers("/common/download/resource**").anonymous() | ||||
| @@ -112,35 +136,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter | ||||
|                 .antMatchers("/swagger-resources/**").anonymous() | ||||
|                 .antMatchers("/webjars/**").anonymous() | ||||
|                 .antMatchers("/*/api-docs").anonymous() | ||||
|                 .antMatchers("/druid/**").anonymous() | ||||
|                 .antMatchers("/druid/**").hasAnyAuthority("druid") // TODO 芋艿,未来需要在拓展下 | ||||
|                 // 除上面外的所有请求全部需要鉴权认证 | ||||
|                 .anyRequest().authenticated() | ||||
|                 .and() | ||||
|                 .headers().frameOptions().disable(); | ||||
|         httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); | ||||
|         // 添加JWT filter | ||||
|         // 添加 JWT Filter | ||||
|         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); | ||||
|         // 添加CORS filter | ||||
|         httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); | ||||
|         httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     /** | ||||
|      * 强散列哈希加密实现 | ||||
|      */ | ||||
|     @Bean | ||||
|     public BCryptPasswordEncoder bCryptPasswordEncoder() | ||||
|     { | ||||
|         return new BCryptPasswordEncoder(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 身份认证接口 | ||||
|      */ | ||||
|     @Override | ||||
|     protected void configure(AuthenticationManagerBuilder auth) throws Exception | ||||
|     { | ||||
|         auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| package cn.iocoder.dashboard.framework.security.config; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
|  | ||||
| import javax.validation.Valid; | ||||
| import javax.validation.constraints.NotEmpty; | ||||
| import javax.validation.constraints.NotNull; | ||||
| import java.time.Duration; | ||||
|  | ||||
| @ConfigurationProperties(prefix = "yudao.security") | ||||
| @Validated | ||||
| @Data | ||||
| public class SecurityProperties { | ||||
|  | ||||
|     /** | ||||
|      * HTTP 请求时,访问令牌的请求 Header | ||||
|      */ | ||||
|     @NotEmpty(message = "Token Header 不能为空") | ||||
|     private String tokenHeader; | ||||
|     /** | ||||
|      * Token 过期时间 | ||||
|      */ | ||||
|     @NotNull(message = "Token 过期时间不能为空") | ||||
|     private Duration tokenTimeout; | ||||
|     /** | ||||
|      * Token 秘钥 | ||||
|      */ | ||||
|     @NotEmpty(message = "Token 秘钥不能为空") | ||||
|     private String tokenSecret; | ||||
|     /** | ||||
|      * Session 过期时间 | ||||
|      * | ||||
|      * 当 User 用户超过当前时间未操作,则 Session 会过期 | ||||
|      */ | ||||
|     @NotNull(message = "Session 过期时间不能为空") | ||||
|     private Duration sessionTimeout; | ||||
|  | ||||
|     /** | ||||
|      * mock 模式的开关 | ||||
|      */ | ||||
|     @NotNull(message = "mock 模式的开关不能为空") | ||||
|     private Boolean mockEnable; | ||||
|     /** | ||||
|      * mock 模式的秘钥 | ||||
|      * 一定要配置秘钥,保证安全性 | ||||
|      */ | ||||
|     @NotEmpty(message = "mock 模式的秘钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。 | ||||
|     private String mockSecret = "yudaoyuanma"; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,90 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core; | ||||
|  | ||||
| import cn.iocoder.dashboard.modules.system.enums.user.UserStatus; | ||||
| import com.alibaba.fastjson.annotation.JSONField; | ||||
| import lombok.Data; | ||||
| import org.springframework.data.annotation.Transient; | ||||
| import org.springframework.security.core.GrantedAuthority; | ||||
| import org.springframework.security.core.userdetails.UserDetails; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Date; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 登陆用户信息 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Data | ||||
| public class LoginUser implements UserDetails { | ||||
|  | ||||
|     /** | ||||
|      * 用户编号 | ||||
|      */ | ||||
|     private Long userId; | ||||
|     /** | ||||
|      * 角色编号数组 | ||||
|      */ | ||||
|     private Set<Integer> roleIds; | ||||
|     /** | ||||
|      * 最后更新时间 | ||||
|      */ | ||||
|     private Date updateTime; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     private String username; | ||||
|     /** | ||||
|      * 密码 | ||||
|      */ | ||||
|     private String password; | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private String status; | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public String getPassword() { | ||||
|         return password; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public String getUsername() { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public boolean isEnabled() { | ||||
|         return UserStatus.OK.getCode().equals(status); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public Collection<? extends GrantedAuthority> getAuthorities() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public boolean isAccountNonExpired() { | ||||
|         return true; // 返回 true,不依赖 Spring Security 判断 | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public boolean isAccountNonLocked() { | ||||
|         return true; // 返回 true,不依赖 Spring Security 判断 | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @JSONField(serialize = false) // 避免序列化 | ||||
|     public boolean isCredentialsNonExpired() { | ||||
|         return true;  // 返回 true,不依赖 Spring Security 判断 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,89 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.filter; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.servlet.ServletUtil; | ||||
| import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||
| import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||
| import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import cn.iocoder.dashboard.framework.web.core.handler.GlobalExceptionHandler; | ||||
| import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; | ||||
| import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||
| import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * JWT 过滤器,验证 token 的有效性 | ||||
|  * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Component | ||||
| public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityProperties securityProperties; | ||||
|     @Resource | ||||
|     private SysAuthService authService; | ||||
|     @Resource | ||||
|     private GlobalExceptionHandler globalExceptionHandler; | ||||
|  | ||||
|     @Override | ||||
|     @SuppressWarnings("NullableProblems") | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||||
|             throws ServletException, IOException { | ||||
|         String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); | ||||
|         if (StrUtil.isNotEmpty(token)) { | ||||
|             try { | ||||
|                 // 验证 token 有效性 | ||||
|                 LoginUser loginUser = authService.verifyTokenAndRefresh(token); | ||||
|                 // 模拟 Login 功能,方便日常开发调试 | ||||
|                 if (loginUser == null) { | ||||
|                     loginUser = this.mockLoginUser(token); | ||||
|                 } | ||||
|                 // 设置当前用户 | ||||
|                 if (loginUser != null) { | ||||
|                     SecurityUtils.setLoginUser(loginUser, request); | ||||
|                 } | ||||
|             } catch (Throwable ex) { | ||||
|                 CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex); | ||||
|                 ServletUtils.writeJSON(response, result); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 继续过滤链 | ||||
|         chain.doFilter(request, response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 模拟登陆用户,方便日常开发调试 | ||||
|      * | ||||
|      * 注意,在线上环境下,一定要关闭该功能!!! | ||||
|      * | ||||
|      * @param token 模拟的 token,格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号 | ||||
|      * @return 模拟的 LoginUser | ||||
|      */ | ||||
|     private LoginUser mockLoginUser(String token) { | ||||
|         if (!securityProperties.getMockEnable()) { | ||||
|             return null; | ||||
|         } | ||||
|         // 必须以 mockSecret 开头 | ||||
|         if (!token.startsWith(securityProperties.getMockSecret())) { | ||||
|             return null; | ||||
|         } | ||||
|         Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length())); | ||||
|         return authService.mockLogin(userId); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.handler; | ||||
|  | ||||
| import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; | ||||
| import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||||
| import org.springframework.security.web.access.ExceptionTranslationFilter; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; | ||||
|  | ||||
| /** | ||||
|  * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 | ||||
|  * | ||||
|  * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Component | ||||
| @Slf4j | ||||
| @SuppressWarnings("JavadocReference") | ||||
| public class AccessDeniedHandlerImpl implements AccessDeniedHandler { | ||||
|  | ||||
|     @Override | ||||
|     public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) | ||||
|             throws IOException, ServletException { | ||||
|         // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 | ||||
|         log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), | ||||
|                 SecurityUtils.getLoginUser().getUserId(), e); | ||||
|         // 返回 403 | ||||
|         ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.handler; | ||||
|  | ||||
| import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; | ||||
| import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||
| import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||||
| import org.springframework.security.web.access.ExceptionTranslationFilter; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; | ||||
|  | ||||
| /** | ||||
|  * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 | ||||
|  * | ||||
|  * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Component | ||||
| @Slf4j | ||||
| @SuppressWarnings("JavadocReference") // 忽略文档引用报错 | ||||
| public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { | ||||
|  | ||||
|     @Override | ||||
|     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { | ||||
|         log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); | ||||
|         // 返回 401 | ||||
|         ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.handler; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 自定义退出处理器 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Component | ||||
| public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityProperties securityProperties; | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityFrameworkService securityFrameworkService; | ||||
|  | ||||
|     @Override | ||||
|     public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { | ||||
|         // 执行退出 | ||||
|         String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); | ||||
|         if (StrUtil.isNotBlank(token)) { | ||||
|             securityFrameworkService.logout(token); | ||||
|         } | ||||
|         // 返回成功 | ||||
|         ServletUtils.writeJSON(response, null); | ||||
| //        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.OK.value(), "退出成功"))); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.service; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
|  | ||||
| /** | ||||
|  * Security 框架 Service 接口,定义 security 组件需要的功能 | ||||
|  */ | ||||
| public interface SecurityFrameworkService extends UserDetailsService { | ||||
|  | ||||
|     /** | ||||
|      * 基于 token 退出登录 | ||||
|      * | ||||
|      * @param token token | ||||
|      */ | ||||
|     void logout(String token); | ||||
|  | ||||
|     /** | ||||
|      * 校验 token 的有效性,并获取用户信息 | ||||
|      * 通过后,刷新 token 的过期时间 | ||||
|      * | ||||
|      * @param token token | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     LoginUser verifyTokenAndRefresh(String token); | ||||
|  | ||||
|     /** | ||||
|      * 模拟指定用户编号的 LoginUser | ||||
|      * | ||||
|      * @param userId 用户编号 | ||||
|      * @return 登录用户 | ||||
|      */ | ||||
|     LoginUser mockLogin(Long userId); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.util; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||
| import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||||
| import org.springframework.util.StringUtils; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| /** | ||||
|  * 安全服务工具类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class SecurityUtils { | ||||
|  | ||||
|     /** | ||||
|      * 从请求中,获得认证 Token | ||||
|      * | ||||
|      * @param request 请求 | ||||
|      * @param header 认证 Token 对应的 Header 名字 | ||||
|      * @return 认证 Token | ||||
|      */ | ||||
|     public static String obtainAuthorization(HttpServletRequest request, String header) { | ||||
|         String authorization = request.getHeader(header); | ||||
|         if (!StringUtils.hasText(authorization)) { | ||||
|             return null; | ||||
|         } | ||||
|         int index = authorization.indexOf("Bearer "); | ||||
|         if (index == -1) { // 未找到 | ||||
|             return null; | ||||
|         } | ||||
|         return authorization.substring(index + 7).trim(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前用户 | ||||
|      */ | ||||
|     public static LoginUser getLoginUser() { | ||||
|         return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置当前用户 | ||||
|      * | ||||
|      * @param loginUser 登陆用户 | ||||
|      * @param request 请求 | ||||
|      */ | ||||
|     public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | ||||
|         // 创建 UsernamePasswordAuthenticationToken 对象 | ||||
|         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | ||||
|                 loginUser, null, null); | ||||
|         authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||||
|         // 设置到上下文 | ||||
|         SecurityContextHolder.getContext().setAuthentication(authenticationToken); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| /** | ||||
|  * 基于 Spring Security 框架 | ||||
|  * 实现安全认证功能 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| package cn.iocoder.dashboard.framework.security; | ||||
| @@ -0,0 +1,2 @@ | ||||
| * 芋道 Spring Security 入门:<http://www.iocoder.cn/Spring-Boot/Spring-Security/?dashboard> | ||||
| * Spring Security 基本概念:<http://www.iocoder.cn/Fight/Spring-Security-4-1-0-Basic-concept-description/?dashboard> | ||||
| @@ -0,0 +1,17 @@ | ||||
| package cn.iocoder.dashboard.modules.system.convert.auth; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.Mapping; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| @Mapper | ||||
| public interface SysAuthConvert { | ||||
|  | ||||
|     SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class); | ||||
|  | ||||
|     @Mapping(source = "updateTime", target = "updateTime", ignore = true) // 字段相同,但是含义不同,忽略 | ||||
|     LoginUser convert(SysUserDO bean); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| package cn.iocoder.dashboard.modules.system.dal.mysql.dao; | ||||
| @@ -0,0 +1,15 @@ | ||||
| package cn.iocoder.dashboard.modules.system.dal.mysql.dao.user; | ||||
|  | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
|  | ||||
| @Mapper | ||||
| public interface SysUserMapper extends BaseMapper<SysUserDO> { | ||||
|  | ||||
|     default SysUserDO selectByUsername(String username) { | ||||
|         return selectOne(new QueryWrapper<SysUserDO>().eq("username", username)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,154 @@ | ||||
| package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user; | ||||
|  | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import javax.validation.constraints.Email; | ||||
| import javax.validation.constraints.NotBlank; | ||||
| import javax.validation.constraints.Size; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.excel.Excel; | ||||
| import cn.iocoder.dashboard.framework.excel.Excels; | ||||
| import cn.iocoder.dashboard.framework.mybatis.core.BaseDO; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDept; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRole; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||||
|  | ||||
| /** | ||||
|  * 用户 DO | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @TableName("sys_user") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| // TODO 芋艿:数据库的字段默认值 | ||||
| public class SysUserDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      */ | ||||
|     @TableId | ||||
|     @Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号") | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     @Excel(name = "部门编号", type = Excel.Type.IMPORT) | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 用户账号 | ||||
|      */ | ||||
|     @Excel(name = "登录名称") | ||||
|     @NotBlank(message = "用户账号不能为空") | ||||
|     @Size(max = 30, message = "用户账号长度不能超过30个字符") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 加密后的密码 | ||||
|      * | ||||
|      * 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐 | ||||
|      */ | ||||
|     private String password; | ||||
|  | ||||
|     /** | ||||
|      * 用户昵称 | ||||
|      */ | ||||
|     @Excel(name = "用户名称") | ||||
|     @Size(max = 30, message = "用户昵称长度不能超过30个字符") | ||||
|     private String nickname; | ||||
|  | ||||
|     /** | ||||
|      * 用户邮箱 | ||||
|      */ | ||||
|     @Excel(name = "用户邮箱") | ||||
|     @Email(message = "邮箱格式不正确") | ||||
|     @Size(max = 50, message = "邮箱长度不能超过50个字符") | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     @Excel(name = "手机号码") | ||||
|     @Size(max = 11, message = "手机号码长度不能超过11个字符") | ||||
|     private String mobile; | ||||
|  | ||||
|     /** | ||||
|      * 用户性别 | ||||
|      */ | ||||
|     @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") | ||||
|     private String sex; | ||||
|  | ||||
|     /** | ||||
|      * 用户头像 | ||||
|      */ | ||||
|     private String avatar; | ||||
|  | ||||
|     /** | ||||
|      * 帐号状态(0正常 1停用) | ||||
|      */ | ||||
|     @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") | ||||
|     // TODO 芋艿:修改成枚举 | ||||
|     private String status; | ||||
|  | ||||
|     /** | ||||
|      * 最后登录IP | ||||
|      */ | ||||
|     @Excel(name = "最后登录IP", type = Excel.Type.EXPORT) | ||||
|     private String loginIp; | ||||
|  | ||||
|     /** | ||||
|      * 最后登录时间 | ||||
|      */ | ||||
|     @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT) | ||||
|     private Date loginDate; | ||||
|  | ||||
|  | ||||
|     // TODO FROM 芋艿:下面的字段,需要忽略 | ||||
|  | ||||
|     /** | ||||
|      * 部门对象 | ||||
|      */ | ||||
|     @Excels({ | ||||
|             @Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT), | ||||
|             @Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT) | ||||
|     }) | ||||
|     @TableField(exist = false) | ||||
|     private SysDept dept; | ||||
|  | ||||
|     /** | ||||
|      * 角色对象 | ||||
|      */ | ||||
|     @TableField(exist = false) | ||||
|     private List<SysRole> roles; | ||||
|  | ||||
|     /** | ||||
|      * 角色组 | ||||
|      */ | ||||
|     @TableField(exist = false) | ||||
|     private Long[] roleIds; | ||||
|  | ||||
|     /** | ||||
|      * 岗位组 | ||||
|      */ | ||||
|     @TableField(exist = false) | ||||
|     private Long[] postIds; | ||||
|  | ||||
|     // TODO 芋艿:后续清理掉 | ||||
|     public boolean isAdmin() { | ||||
|         return isAdmin(this.userId); | ||||
|     } | ||||
|  | ||||
|     public static boolean isAdmin(Long userId) { | ||||
|         return userId != null && 1L == userId; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| package cn.iocoder.dashboard.modules.system.enums; | ||||
|  | ||||
| import cn.iocoder.dashboard.common.exception.ErrorCode; | ||||
|  | ||||
| /** | ||||
|  * 错误码枚举类 | ||||
|  * | ||||
|  * system 系统,使用 1-002-000-000 段 | ||||
|  */ | ||||
| public interface SysErrorCodeConstants { | ||||
|  | ||||
|     // ========== AUTH 模块 1002000000 ========== | ||||
|     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); | ||||
|     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); | ||||
|     ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底,位置原因 | ||||
|  | ||||
|     // ========== TOKEN 模块 1002001000 ========== | ||||
|     ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期"); | ||||
|     ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败"); | ||||
|  | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package com.ruoyi.common.enums; | ||||
| package cn.iocoder.dashboard.modules.system.enums.user; | ||||
| 
 | ||||
| /** | ||||
|  * 用户状态 | ||||
| @@ -0,0 +1,16 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.auth; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||
|  | ||||
| /** | ||||
|  * 认证 Service 接口 | ||||
|  * | ||||
|  * 提供用户的账号密码登陆、token 的校验等认证相关的功能 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public interface SysAuthService extends SecurityFrameworkService { | ||||
|  | ||||
|     String login(String username, String password, String captchaUUID, String captchaCode); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.auth; | ||||
|  | ||||
| import io.jsonwebtoken.Claims; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Token Service 接口 | ||||
|  * | ||||
|  * 提供访问 Token 令牌,目前基于 JWT 实现 | ||||
|  */ | ||||
| public interface SysTokenService { | ||||
|  | ||||
|     /** | ||||
|      * 创建 Token | ||||
|      * | ||||
|      * @param subject 主体 | ||||
|      * @return Token 字符串 | ||||
|      */ | ||||
|     String createToken(String subject); | ||||
|  | ||||
|     /** | ||||
|      * 解析 Token,返回 claims 数据声明 | ||||
|      * | ||||
|      * @param token Token | ||||
|      * @return claims | ||||
|      */ | ||||
|     Claims parseToken(String token); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,215 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.auth.impl; | ||||
|  | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||
| import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import cn.iocoder.dashboard.modules.system.enums.user.UserStatus; | ||||
| import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||
| import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO; | ||||
| import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; | ||||
| import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; | ||||
| import cn.iocoder.dashboard.modules.system.service.user.SysUserService; | ||||
| import cn.iocoder.dashboard.util.date.DateUtils; | ||||
| import io.jsonwebtoken.Claims; | ||||
| import io.jsonwebtoken.JwtException; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.security.authentication.AuthenticationManager; | ||||
| import org.springframework.security.authentication.BadCredentialsException; | ||||
| import org.springframework.security.authentication.DisabledException; | ||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.core.userdetails.UserDetails; | ||||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.util.Assert; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.Set; | ||||
|  | ||||
| import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; | ||||
|  | ||||
| /** | ||||
|  * Auth Service 实现类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Service | ||||
| @Slf4j | ||||
| public class SysAuthServiceImpl implements SysAuthService { | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityProperties securityProperties; | ||||
|  | ||||
|     @Resource | ||||
|     private SysTokenService tokenService; | ||||
|     @Resource | ||||
|     private AuthenticationManager authenticationManager; | ||||
|     @Resource | ||||
|     private SysUserService userService; | ||||
|     @Resource | ||||
|     private SysLoginUserRedisDAO loginUserRedisDAO; | ||||
|  | ||||
|     @Override | ||||
|     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||||
|         // 获取 username 对应的 SysUserDO | ||||
|         SysUserDO user = userService.getUserByUserName(username); | ||||
|         if (user == null) { | ||||
|             throw new UsernameNotFoundException(username); | ||||
|         } | ||||
|  | ||||
|         // 创建 LoginUser 对象 | ||||
|         return SysAuthConvert.INSTANCE.convert(user); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public LoginUser mockLogin(Long userId) { | ||||
|         // 获取用户编号对应的 SysUserDO | ||||
|         SysUserDO user = userService.getUser(userId); | ||||
|         if (user == null) { | ||||
|             throw new UsernameNotFoundException(String.valueOf(userId)); | ||||
|         } | ||||
|  | ||||
|         // 创建 LoginUser 对象 | ||||
|         LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | ||||
|         loginUser.setUpdateTime(new Date()); | ||||
|         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||
|         return loginUser; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String login(String username, String password, String captchaUUID, String captchaCode) { | ||||
|         // 判断验证码是否正确 | ||||
|         this.verifyCaptcha(captchaUUID, captchaCode); | ||||
|  | ||||
|         // 使用账号密码,进行登陆。 | ||||
|         LoginUser loginUser = this.login0(username, password); | ||||
|  | ||||
|         // 缓存登陆用户到 Redis 中 | ||||
|         String sessionId = IdUtil.fastSimpleUUID(); | ||||
|         loginUser.setUpdateTime(new Date()); | ||||
|         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||
|         loginUserRedisDAO.set(sessionId, loginUser); | ||||
|  | ||||
|         // 创建 Token | ||||
|         // 我们在返回给前端的 JWT 中,使用 sessionId 作为 subject 主体,标识当前 User 用户 | ||||
|         return tokenService.createToken(sessionId); | ||||
|     } | ||||
|  | ||||
|     private void verifyCaptcha(String captchaUUID, String captchaCode) { | ||||
|         //        String verifyKey = Constants.CAPTCHA_CODE_KEY + captchaUUID; | ||||
| //        String captcha = redisCache.getCacheObject(verifyKey); | ||||
| //        redisCache.deleteObject(verifyKey); | ||||
| //        if (captcha == null) { | ||||
| //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); | ||||
| //            throw new CaptchaExpireException(); | ||||
| //        } | ||||
| //        if (!code.equalsIgnoreCase(captcha)) { | ||||
| //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); | ||||
| //            throw new CaptchaException(); | ||||
| //        } | ||||
|     } | ||||
|  | ||||
|     private LoginUser login0(String username, String password) { | ||||
|         // 用户验证 | ||||
|         Authentication authentication; | ||||
|         try { | ||||
|             // 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证 | ||||
|             // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息 | ||||
|             authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); | ||||
|         } catch (BadCredentialsException badCredentialsException) { | ||||
|             // TODO 日志优化 | ||||
| //            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); | ||||
|             throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||||
|         } catch (DisabledException disabledException) { | ||||
|             // TODO 日志优化 | ||||
|             throw exception(AUTH_LOGIN_USER_DISABLED); | ||||
|         } catch (AuthenticationException authenticationException) { | ||||
|             // TODO 日志优化 | ||||
|             throw exception(AUTH_LOGIN_FAIL_UNKNOWN); | ||||
|         } | ||||
|         // TODO 需要优化 | ||||
| //        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); | ||||
|         Assert.notNull(authentication.getPrincipal(), "Principal 不会为空"); | ||||
|         return (LoginUser) authentication.getPrincipal(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得 User 拥有的角色编号数组 | ||||
|      * | ||||
|      * @param userId 用户编号 | ||||
|      * @return 角色编号数组 | ||||
|      */ | ||||
|     private Set<Integer> getUserRoleIds(Long userId) { | ||||
|         // TODO 芋艿:读取角色编号 | ||||
|         return Collections.emptySet(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void logout(String token) { | ||||
| //        AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); TODO 需要搞一搞 | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public LoginUser verifyTokenAndRefresh(String token) { | ||||
|         // 验证 token 的有效性 | ||||
|         String sessionId = this.verifyToken(token); | ||||
|         // 获得 LoginUser | ||||
|         LoginUser loginUser = loginUserRedisDAO.get(sessionId); | ||||
|         if (loginUser == null) { | ||||
| //            throw exception(AUTH_SESSION_TIMEOUT); | ||||
|             return null; | ||||
|         } | ||||
|         // 刷新 LoginUser 缓存 | ||||
|         this.refreshLoginUserCache(sessionId, loginUser); | ||||
|         return loginUser; | ||||
|     } | ||||
|  | ||||
|     private String verifyToken(String token) { | ||||
|         Claims claims; | ||||
|         try { | ||||
|             claims = tokenService.parseToken(token); | ||||
|         } catch (JwtException jwtException) { | ||||
|             log.warn("[verifyToken][token({}) 解析发生异常]", token); | ||||
| //            throw exception(TOKEN_PARSE_FAIL); | ||||
|             return null; | ||||
|         } | ||||
|         // token 已经过期 | ||||
|         if (DateUtils.isExpired(claims.getExpiration())) { | ||||
| //            throw exception(TOKEN_EXPIRED); | ||||
|             return null; | ||||
|         } | ||||
|         // 判断 sessionId 是否存在 | ||||
|         String sessionId = claims.getSubject(); | ||||
|         if (StrUtil.isBlank(sessionId)) { | ||||
| //            throw exception(AUTH_SESSION_ID_NOT_FOUND); | ||||
|             return null; | ||||
|         } | ||||
|         return sessionId; | ||||
|     } | ||||
|  | ||||
|     private void refreshLoginUserCache(String sessionId, LoginUser loginUser) { | ||||
|         // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 | ||||
|         if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < | ||||
|                 securityProperties.getSessionTimeout().toMillis() / 3) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 重新加载 SysUserDO 信息 | ||||
|         SysUserDO user = userService.getUser(loginUser.getUserId()); | ||||
|         if (user == null || UserStatus.DISABLE.getCode().equals(user.getStatus())) { | ||||
|             throw exception(TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登陆界面 | ||||
|         } | ||||
|  | ||||
|         // 刷新 LoginUser 缓存 | ||||
|         loginUser.setUpdateTime(new Date()); | ||||
|         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getUserId())); | ||||
|         loginUserRedisDAO.set(sessionId, loginUser); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,58 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.auth.impl; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||
| import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; | ||||
| import cn.iocoder.dashboard.util.date.DateUtils; | ||||
| import io.jsonwebtoken.Claims; | ||||
| import io.jsonwebtoken.Jwts; | ||||
| import io.jsonwebtoken.SignatureAlgorithm; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Token Service 实现类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Service | ||||
| public class SysTokenServiceImpl implements SysTokenService { | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityProperties securityProperties; | ||||
|  | ||||
|     @Override | ||||
|     public String createToken(String subject) { | ||||
|         return Jwts.builder() | ||||
|                 .signWith(SignatureAlgorithm.HS512, securityProperties.getTokenSecret()) | ||||
|                 .setExpiration(DateUtils.addTime(securityProperties.getTokenTimeout())) | ||||
|                 .setSubject(subject) | ||||
|                 .compact(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Claims parseToken(String token) { | ||||
|         return Jwts.parser() | ||||
|                 .setSigningKey(securityProperties.getTokenSecret()) | ||||
|                 .parseClaimsJws(token) | ||||
|                 .getBody(); | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         String secret = "abcdefghijklmnopqrstuvwxyz"; | ||||
|         Map<String, Object> map = new HashMap<>(); | ||||
|         map.put("key1", "value1"); | ||||
|         System.out.println(Jwts.builder() | ||||
|                 .signWith(SignatureAlgorithm.HS512, secret) | ||||
|                 .setClaims(map) | ||||
|                 .compact()); | ||||
|  | ||||
|         System.out.println(Jwts.parser() | ||||
|                 .setSigningKey(secret) | ||||
|                 .parseClaimsJws("qyJhbGciOiJIUzUxMiJ9.eyJrZXkxIjoidmFsdWUxIn0.AHWncLRBlJkqrKaoWHZmMgbqYIT7rfLs8KCp9LuC0mdNfnx1xEMm1N9bgcD-0lc5sjySqsKiWzqJ3rpoyUSh0g") | ||||
|                 .getBody()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,166 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.user; | ||||
|  | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||
|  | ||||
| /** | ||||
|  * 用户 Service 接口 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public interface SysUserService { | ||||
| //    /** | ||||
| //     * 根据条件分页查询用户列表 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 用户信息集合信息 | ||||
| //     */ | ||||
| //    public List<SysUser> selectUserList(SysUser user); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户名查询用户 | ||||
|      * | ||||
|      * @param username 用户名 | ||||
|      * @return 用户对象信息 | ||||
|      */ | ||||
|     SysUserDO getUserByUserName(String username); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户 ID 查询用户 | ||||
|      * | ||||
|      * @param userId 用户ID | ||||
|      * @return 用户对象信息 | ||||
|      */ | ||||
|     SysUserDO getUser(Long userId); | ||||
|  | ||||
| // | ||||
| //    /** | ||||
| //     * 根据用户ID查询用户所属角色组 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String selectUserRoleGroup(String userName); | ||||
| // | ||||
| //    /** | ||||
| //     * 根据用户ID查询用户所属岗位组 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String selectUserPostGroup(String userName); | ||||
| // | ||||
| //    /** | ||||
| //     * 校验用户名称是否唯一 | ||||
| //     * | ||||
| //     * @param userName 用户名称 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String checkUserNameUnique(String userName); | ||||
| // | ||||
| //    /** | ||||
| //     * 校验手机号码是否唯一 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String checkPhoneUnique(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 校验email是否唯一 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String checkEmailUnique(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 校验用户是否允许操作 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     */ | ||||
| //    public void checkUserAllowed(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 新增用户信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int insertUser(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int updateUser(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户状态 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int updateUserStatus(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户基本信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int updateUserProfile(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户头像 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @param avatar 头像地址 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public boolean updateUserAvatar(String userName, String avatar); | ||||
| // | ||||
| //    /** | ||||
| //     * 重置用户密码 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int resetPwd(SysUser user); | ||||
| // | ||||
| //    /** | ||||
| //     * 重置用户密码 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @param password 密码 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int resetUserPwd(String userName, String password); | ||||
| // | ||||
| //    /** | ||||
| //     * 通过用户ID删除用户 | ||||
| //     * | ||||
| //     * @param userId 用户ID | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int deleteUserById(Long userId); | ||||
| // | ||||
| //    /** | ||||
| //     * 批量删除用户信息 | ||||
| //     * | ||||
| //     * @param userIds 需要删除的用户ID | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public int deleteUserByIds(Long[] userIds); | ||||
| // | ||||
| //    /** | ||||
| //     * 导入用户数据 | ||||
| //     * | ||||
| //     * @param userList 用户数据列表 | ||||
| //     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 | ||||
| //     * @param operName 操作用户 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName); | ||||
| } | ||||
| @@ -0,0 +1,447 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.user; | ||||
|  | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dao.user.SysUserMapper; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 用户 业务层处理 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Service | ||||
| @Slf4j | ||||
| public class SysUserServiceImpl implements SysUserService { | ||||
|  | ||||
|     @Resource | ||||
|     private SysUserMapper userMapper; | ||||
|  | ||||
| //    @Autowired | ||||
| //    private SysUserMapper userMapper; | ||||
| // | ||||
| //    @Autowired | ||||
| //    private SysRoleMapper roleMapper; | ||||
| // | ||||
| //    @Autowired | ||||
| //    private SysPostMapper postMapper; | ||||
| // | ||||
| //    @Autowired | ||||
| //    private SysUserRoleMapper userRoleMapper; | ||||
| // | ||||
| //    @Autowired | ||||
| //    private SysUserPostMapper userPostMapper; | ||||
| // | ||||
| //    @Autowired | ||||
| //    private ISysConfigService configService; | ||||
|  | ||||
| //    /** | ||||
| //     * 根据条件分页查询用户列表 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 用户信息集合信息 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    @DataScope(deptAlias = "d", userAlias = "u") | ||||
| //    public List<SysUser> selectUserList(SysUser user) | ||||
| //    { | ||||
| //        return userMapper.selectUserList(user); | ||||
| //    } | ||||
|  | ||||
|     @Override | ||||
|     public SysUserDO getUserByUserName(String username) { | ||||
|         return userMapper.selectByUsername(username); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public SysUserDO getUser(Long userId) { | ||||
|         return userMapper.selectById(userId); | ||||
|     } | ||||
|  | ||||
| //    /** | ||||
| //     * 通过用户ID查询用户 | ||||
| //     * | ||||
| //     * @param userId 用户ID | ||||
| //     * @return 用户对象信息 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public SysUser selectUserById(Long userId) | ||||
| //    { | ||||
| //        return userMapper.selectUserById(userId); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 查询用户所属角色组 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String selectUserRoleGroup(String userName) | ||||
| //    { | ||||
| //        List<SysRole> list = roleMapper.selectRolesByUserName(userName); | ||||
| //        StringBuffer idsStr = new StringBuffer(); | ||||
| //        for (SysRole role : list) | ||||
| //        { | ||||
| //            idsStr.append(role.getRoleName()).append(","); | ||||
| //        } | ||||
| //        if (StringUtils.isNotEmpty(idsStr.toString())) | ||||
| //        { | ||||
| //            return idsStr.substring(0, idsStr.length() - 1); | ||||
| //        } | ||||
| //        return idsStr.toString(); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 查询用户所属岗位组 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String selectUserPostGroup(String userName) | ||||
| //    { | ||||
| //        List<SysPost> list = postMapper.selectPostsByUserName(userName); | ||||
| //        StringBuffer idsStr = new StringBuffer(); | ||||
| //        for (SysPost post : list) | ||||
| //        { | ||||
| //            idsStr.append(post.getPostName()).append(","); | ||||
| //        } | ||||
| //        if (StringUtils.isNotEmpty(idsStr.toString())) | ||||
| //        { | ||||
| //            return idsStr.substring(0, idsStr.length() - 1); | ||||
| //        } | ||||
| //        return idsStr.toString(); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 校验用户名称是否唯一 | ||||
| //     * | ||||
| //     * @param userName 用户名称 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String checkUserNameUnique(String userName) | ||||
| //    { | ||||
| //        int count = userMapper.checkUserNameUnique(userName); | ||||
| //        if (count > 0) | ||||
| //        { | ||||
| //            return UserConstants.NOT_UNIQUE; | ||||
| //        } | ||||
| //        return UserConstants.UNIQUE; | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 校验用户名称是否唯一 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String checkPhoneUnique(SysUser user) | ||||
| //    { | ||||
| //        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); | ||||
| //        SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); | ||||
| //        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) | ||||
| //        { | ||||
| //            return UserConstants.NOT_UNIQUE; | ||||
| //        } | ||||
| //        return UserConstants.UNIQUE; | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 校验email是否唯一 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String checkEmailUnique(SysUser user) | ||||
| //    { | ||||
| //        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); | ||||
| //        SysUser info = userMapper.checkEmailUnique(user.getEmail()); | ||||
| //        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) | ||||
| //        { | ||||
| //            return UserConstants.NOT_UNIQUE; | ||||
| //        } | ||||
| //        return UserConstants.UNIQUE; | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 校验用户是否允许操作 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public void checkUserAllowed(SysUser user) | ||||
| //    { | ||||
| //        if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) | ||||
| //        { | ||||
| //            throw new CustomException("不允许操作超级管理员用户"); | ||||
| //        } | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 新增保存用户信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    @Transactional | ||||
| //    public int insertUser(SysUser user) | ||||
| //    { | ||||
| //        // 新增用户信息 | ||||
| //        int rows = userMapper.insertUser(user); | ||||
| //        // 新增用户岗位关联 | ||||
| //        insertUserPost(user); | ||||
| //        // 新增用户与角色管理 | ||||
| //        insertUserRole(user); | ||||
| //        return rows; | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 修改保存用户信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    @Transactional | ||||
| //    public int updateUser(SysUser user) | ||||
| //    { | ||||
| //        Long userId = user.getUserId(); | ||||
| //        // 删除用户与角色关联 | ||||
| //        userRoleMapper.deleteUserRoleByUserId(userId); | ||||
| //        // 新增用户与角色管理 | ||||
| //        insertUserRole(user); | ||||
| //        // 删除用户与岗位关联 | ||||
| //        userPostMapper.deleteUserPostByUserId(userId); | ||||
| //        // 新增用户与岗位管理 | ||||
| //        insertUserPost(user); | ||||
| //        return userMapper.updateUser(user); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户状态 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public int updateUserStatus(SysUser user) | ||||
| //    { | ||||
| //        return userMapper.updateUser(user); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户基本信息 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public int updateUserProfile(SysUser user) | ||||
| //    { | ||||
| //        return userMapper.updateUser(user); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 修改用户头像 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @param avatar 头像地址 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public boolean updateUserAvatar(String userName, String avatar) | ||||
| //    { | ||||
| //        return userMapper.updateUserAvatar(userName, avatar) > 0; | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 重置用户密码 | ||||
| //     * | ||||
| //     * @param user 用户信息 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public int resetPwd(SysUser user) | ||||
| //    { | ||||
| //        return userMapper.updateUser(user); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 重置用户密码 | ||||
| //     * | ||||
| //     * @param userName 用户名 | ||||
| //     * @param password 密码 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public int resetUserPwd(String userName, String password) | ||||
| //    { | ||||
| //        return userMapper.resetUserPwd(userName, password); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 新增用户角色信息 | ||||
| //     * | ||||
| //     * @param user 用户对象 | ||||
| //     */ | ||||
| //    public void insertUserRole(SysUser user) | ||||
| //    { | ||||
| //        Long[] roles = user.getRoleIds(); | ||||
| //        if (StringUtils.isNotNull(roles)) | ||||
| //        { | ||||
| //            // 新增用户与角色管理 | ||||
| //            List<SysUserRole> list = new ArrayList<SysUserRole>(); | ||||
| //            for (Long roleId : roles) | ||||
| //            { | ||||
| //                SysUserRole ur = new SysUserRole(); | ||||
| //                ur.setUserId(user.getUserId()); | ||||
| //                ur.setRoleId(roleId); | ||||
| //                list.add(ur); | ||||
| //            } | ||||
| //            if (list.size() > 0) | ||||
| //            { | ||||
| //                userRoleMapper.batchUserRole(list); | ||||
| //            } | ||||
| //        } | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 新增用户岗位信息 | ||||
| //     * | ||||
| //     * @param user 用户对象 | ||||
| //     */ | ||||
| //    public void insertUserPost(SysUser user) | ||||
| //    { | ||||
| //        Long[] posts = user.getPostIds(); | ||||
| //        if (StringUtils.isNotNull(posts)) | ||||
| //        { | ||||
| //            // 新增用户与岗位管理 | ||||
| //            List<SysUserPost> list = new ArrayList<SysUserPost>(); | ||||
| //            for (Long postId : posts) | ||||
| //            { | ||||
| //                SysUserPost up = new SysUserPost(); | ||||
| //                up.setUserId(user.getUserId()); | ||||
| //                up.setPostId(postId); | ||||
| //                list.add(up); | ||||
| //            } | ||||
| //            if (list.size() > 0) | ||||
| //            { | ||||
| //                userPostMapper.batchUserPost(list); | ||||
| //            } | ||||
| //        } | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 通过用户ID删除用户 | ||||
| //     * | ||||
| //     * @param userId 用户ID | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    @Transactional | ||||
| //    public int deleteUserById(Long userId) | ||||
| //    { | ||||
| //        // 删除用户与角色关联 | ||||
| //        userRoleMapper.deleteUserRoleByUserId(userId); | ||||
| //        // 删除用户与岗位表 | ||||
| //        userPostMapper.deleteUserPostByUserId(userId); | ||||
| //        return userMapper.deleteUserById(userId); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 批量删除用户信息 | ||||
| //     * | ||||
| //     * @param userIds 需要删除的用户ID | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    @Transactional | ||||
| //    public int deleteUserByIds(Long[] userIds) | ||||
| //    { | ||||
| //        for (Long userId : userIds) | ||||
| //        { | ||||
| //            checkUserAllowed(new SysUser(userId)); | ||||
| //        } | ||||
| //        // 删除用户与角色关联 | ||||
| //        userRoleMapper.deleteUserRole(userIds); | ||||
| //        // 删除用户与岗位关联 | ||||
| //        userPostMapper.deleteUserPost(userIds); | ||||
| //        return userMapper.deleteUserByIds(userIds); | ||||
| //    } | ||||
| // | ||||
| //    /** | ||||
| //     * 导入用户数据 | ||||
| //     * | ||||
| //     * @param userList 用户数据列表 | ||||
| //     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 | ||||
| //     * @param operName 操作用户 | ||||
| //     * @return 结果 | ||||
| //     */ | ||||
| //    @Override | ||||
| //    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName) | ||||
| //    { | ||||
| //        if (StringUtils.isNull(userList) || userList.size() == 0) | ||||
| //        { | ||||
| //            throw new CustomException("导入用户数据不能为空!"); | ||||
| //        } | ||||
| //        int successNum = 0; | ||||
| //        int failureNum = 0; | ||||
| //        StringBuilder successMsg = new StringBuilder(); | ||||
| //        StringBuilder failureMsg = new StringBuilder(); | ||||
| //        String password = configService.selectConfigByKey("sys.user.initPassword"); | ||||
| //        for (SysUser user : userList) | ||||
| //        { | ||||
| //            try | ||||
| //            { | ||||
| //                // 验证是否存在这个用户 | ||||
| //                SysUser u = userMapper.selectUserByUserName(user.getUserName()); | ||||
| //                if (StringUtils.isNull(u)) | ||||
| //                { | ||||
| //                    user.setPassword(SecurityUtils.encryptPassword(password)); | ||||
| //                    user.setCreateBy(operName); | ||||
| //                    this.insertUser(user); | ||||
| //                    successNum++; | ||||
| //                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功"); | ||||
| //                } | ||||
| //                else if (isUpdateSupport) | ||||
| //                { | ||||
| //                    user.setUpdateBy(operName); | ||||
| //                    this.updateUser(user); | ||||
| //                    successNum++; | ||||
| //                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功"); | ||||
| //                } | ||||
| //                else | ||||
| //                { | ||||
| //                    failureNum++; | ||||
| //                    failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在"); | ||||
| //                } | ||||
| //            } | ||||
| //            catch (Exception e) | ||||
| //            { | ||||
| //                failureNum++; | ||||
| //                String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; | ||||
| //                failureMsg.append(msg + e.getMessage()); | ||||
| //                log.error(msg, e); | ||||
| //            } | ||||
| //        } | ||||
| //        if (failureNum > 0) | ||||
| //        { | ||||
| //            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); | ||||
| //            throw new CustomException(failureMsg.toString()); | ||||
| //        } | ||||
| //        else | ||||
| //        { | ||||
| //            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); | ||||
| //        } | ||||
| //        return successMsg.toString(); | ||||
| //    } | ||||
|  | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| package cn.iocoder.dashboard.util.date; | ||||
|  | ||||
| import java.time.Duration; | ||||
| import java.util.Date; | ||||
|  | ||||
| /** | ||||
|  * 时间工具类 | ||||
|  */ | ||||
| public class DateUtils { | ||||
|  | ||||
|     public static Date addTime(Duration duration) { | ||||
|         return new Date(System.currentTimeMillis() + duration.toMillis()); | ||||
|     } | ||||
|  | ||||
|     public static boolean isExpired(Date time) { | ||||
|         return System.currentTimeMillis() > time.getTime(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| package cn.iocoder.dashboard.util.servlet; | ||||
|  | ||||
| import cn.hutool.extra.servlet.ServletUtil; | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import org.springframework.http.MediaType; | ||||
|  | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| /** | ||||
|  * 客户端工具类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class ServletUtils { | ||||
|  | ||||
|     @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 | ||||
|     public static void writeJSON(HttpServletResponse response, Object object) { | ||||
|         String content = JSON.toJSONString(object); | ||||
|         ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -27,6 +27,8 @@ yudao: | ||||
|     token-secret: abcdefghijklmnopqrstuvwxyz | ||||
|     token-timeout: 1d | ||||
|     session-timeout: 30m | ||||
|     mock-enable: true | ||||
|     mock-secret: yudaoyuanma | ||||
|   swagger: | ||||
|     title: 管理后台 | ||||
|     description: 提供管理员管理的所有功能 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV