0%

若依用户分表鉴权

若依用户分表鉴权

若依自带了管理后台及服务端,但项目通常还有客户端业务。那客户端如何实现鉴权?最简单的方式是在原有的 sys_user 上增加业务逻辑,但随着项目越做越大耦合度也会成倍增加。那解耦就势在必行。

本篇将介绍如何让客户端拥有一套独立表来实现用户鉴权。完整代码参见

创建 auths Maven 模块

一个项目有可能有多个业务用户鉴权需求,所有的鉴权都放在这个模块中进行管理。

接下来我们将以创建 news 业务出发,目录文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
codezm-auths
├── pom.xml
└── src
└── main
├── java
│   └── com
│   └── codezm
│   └── auths
│   └── news
│   ├── config
│   │   ├── NewsSecurityConfig.java
│   │   └── properties
│   │   └── PermitAllUrlProperties.java
│   ├── constant
│   │   └── CacheConstants.java
│   ├── domain
│   │   ├── NewsLoginBody.java
│   │   ├── NewsLoginUser.java
│   │   └── NewsUser.java
│   ├── manager
│   │   ├── AsyncManager.java
│   │   └── factory
│   │   └── AsyncFactory.java
│   ├── mapper
│   │   └── LoginUserMapper.java
│   ├── security
│   │   ├── UserPwdAuthenticationProvider.java
│   │   ├── UserPwdAuthenticationToken.java
│   │   ├── context
│   │   │   ├── AuthenticationContextHolder.java
│   │   │   └── PermissionContextHolder.java
│   │   ├── filter
│   │   │   └── JwtAuthenticationTokenFilter.java
│   │   └── handle
│   │   ├── AuthenticationEntryPointImpl.java
│   │   └── LogoutSuccessHandlerImpl.java
│   ├── service
│   │   ├── ILoginUserService.java
│   │   ├── LoginService.java
│   │   ├── TokenService.java
│   │   ├── UserPwdServiceImpl.java
│   │   └── impl
│   │   └── LoginUserServiceImpl.java
│   └── untils
│   └── SecurityUtils.java
└── resources
└── mapper
└── news
└── LoginUserMapper.xml

auths/src/main/java/com/codezm/auths/news/config/NewsSecurityConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.codezm.auths.news.config;


import com.codezm.auths.news.config.properties.PermitAllUrlProperties;
import com.codezm.auths.news.security.UserPwdAuthenticationProvider;
import com.codezm.auths.news.security.filter.JwtAuthenticationTokenFilter;
import com.codezm.auths.news.security.handle.AuthenticationEntryPointImpl;
import com.codezm.auths.news.security.handle.LogoutSuccessHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

/**
* spring security配置
*
* @author ruoyi
*/

//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
//@EnableWebSecurity
@Order(1)
@Configuration
public class NewsSecurityConfig extends WebSecurityConfigurerAdapter
{


/**
* 系统前台用户身份认证逻辑
*/
@Autowired
@Qualifier("NewsUserPwdServiceImpl")
private UserDetailsService userPwdService;

/**
* 认证失败处理类
*/
@Autowired
@Qualifier("NewsAuthenticationEntryPointImpl")
private AuthenticationEntryPointImpl unauthorizedHandler;

/**
* 退出处理类
*/
@Autowired
@Qualifier("NewsLogoutSuccessHandlerImpl")
private LogoutSuccessHandlerImpl logoutSuccessHandler;

/**
* token认证过滤器
*/
@Autowired
@Qualifier("NewsJwtAuthenticationTokenFilter")
private JwtAuthenticationTokenFilter authenticationTokenFilter;

/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;

/**
* 允许匿名访问的地址
*/
@Autowired
@Qualifier("NewsPermitAllUrlProperties")
private PermitAllUrlProperties permitAllUrl;


/**
* 解决 无法直接注入 AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean("NewsAuthenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{

return super.authenticationManagerBean();
}

/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

httpSecurity
.requestMatchers((request) -> {
request.antMatchers("/news/**");
})
// CSRF禁用,因为不使用session
.csrf().disable()
// 禁用HTTP响应标头
.headers().cacheControl().disable().and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/news/login", "/news/register", "/news/captchaImage").anonymous()
// 静态资源,可匿名访问
// .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**","/static/**").permitAll()
// .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加Logout filter
httpSecurity.logout().logoutUrl("/news/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}

/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
UserPwdAuthenticationProvider userPwdAuthenticationProvider = new UserPwdAuthenticationProvider();
userPwdAuthenticationProvider.setUserDetailsService(userPwdService);
auth.authenticationProvider(userPwdAuthenticationProvider);
auth.userDetailsService(userPwdService).passwordEncoder(new BCryptPasswordEncoder());
}
}

auths/src/main/java/com/codezm/auths/news/config/properties/PermitAllUrlProperties.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.codezm.auths.news.config.properties;

import com.ruoyi.common.annotation.Anonymous;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;
import java.util.regex.Pattern;

/**
* 设置Anonymous注解允许匿名访问的url
*
* @author ruoyi
*/
@Component("NewsPermitAllUrlProperties")
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

private ApplicationContext applicationContext;

private List<String> urls = new ArrayList<>();

public String ASTERISK = "*";

@Override
public void afterPropertiesSet()
{
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);

// 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

// 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
});
}

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}

public List<String> getUrls()
{
return urls;
}

public void setUrls(List<String> urls)
{
this.urls = urls;
}
}

auths/src/main/java/com/codezm/auths/news/constant/CacheConstants.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.codezm.auths.news.constant;

/**
* 缓存的key 常量
*
* @author ruoyi
*/
public class CacheConstants
{
/**
* 登录用户 redis key
*/
public static final String LOGIN_TOKEN_KEY = "login_tokens_news:";

/**
* 验证码 redis key
*/
public static final String CAPTCHA_CODE_KEY = "captcha_codes:";

/**
* 参数管理 cache key
*/
public static final String SYS_CONFIG_KEY = "sys_config:";

/**
* 字典管理 cache key
*/
public static final String SYS_DICT_KEY = "sys_dict:";

/**
* 防重提交 redis key
*/
public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";

/**
* 限流 redis key
*/
public static final String RATE_LIMIT_KEY = "rate_limit:";

/**
* 登录账户密码错误次数 redis key
*/
public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";

}

auths/src/main/java/com/codezm/auths/news/domain/NewsLoginBody.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.codezm.auths.news.domain;

/**
* 用户登录对象
*
* @author ruoyi
*/
public class NewsLoginBody
{
/**
* 用户名
*/
private String username;

/**
* 用户密码
*/
private String password;

/**
* 验证码
*/
private String code;

/**
* 唯一标识
*/
private String uuid;

public String getUsername()
{
return username;
}

public void setUsername(String username)
{
this.username = username;
}

public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

public String getCode()
{
return code;
}

public void setCode(String code)
{
this.code = code;
}

public String getUuid()
{
return uuid;
}

public void setUuid(String uuid)
{
this.uuid = uuid;
}
}

auths/src/main/java/com/codezm/auths/news/domain/NewsLoginUser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package com.codezm.auths.news.domain;

import com.alibaba.fastjson2.annotation.JSONField;
import com.codezm.auths.news.domain.NewsUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

/**
* 登录用户身份权限
*
* @author ruoyi
*/
public class NewsLoginUser implements UserDetails
{
private static final long serialVersionUID = 1L;

/**
* 用户ID
*/
private Long userId;

/**
* 部门ID
*/
private Long deptId;

/**
* openId
*/
private String openId;

/**
* 用户唯一标识
*/
private String token;

/**
* 登录时间
*/
private Long loginTime;

/**
* 过期时间
*/
private Long expireTime;

/**
* 登录IP地址
*/
private String ipaddr;

/**
* 登录地点
*/
private String loginLocation;

/**
* 浏览器类型
*/
private String browser;

/**
* 操作系统
*/
private String os;

/**
* 权限列表
*/
private Set<String> permissions;

/**
* 用户信息
*/
private NewsUser newsUser;

public NewsLoginUser()
{
}

public NewsLoginUser(NewsUser newsUser, Set<String> permissions)
{
this.newsUser = newsUser;
this.permissions = permissions;
}

public NewsLoginUser(Long userId, Long deptId, NewsUser newsUser)
{
this.userId = userId;
this.deptId = deptId;
this.newsUser = newsUser;
//this.permissions = permissions;
}

public Long getUserId()
{
return userId;
}

public void setUserId(Long userId)
{
this.userId = userId;
}

public Long getDeptId()
{
return deptId;
}

public void setDeptId(Long deptId)
{
this.deptId = deptId;
}

public String getToken()
{
return token;
}

public void setToken(String token)
{
this.token = token;
}

public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}

@JSONField(serialize = false)
@Override
public String getPassword()
{
return newsUser.getPassword();
}

@Override
public String getUsername()
{
return newsUser.getUserName();
}

/**
* 账户是否未过期,过期无法验证
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired()
{
return true;
}

/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonLocked()
{
return true;
}

/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isCredentialsNonExpired()
{
return true;
}

/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isEnabled()
{
return true;
}

public Long getLoginTime()
{
return loginTime;
}

public void setLoginTime(Long loginTime)
{
this.loginTime = loginTime;
}

public String getIpaddr()
{
return ipaddr;
}

public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
}

public String getLoginLocation()
{
return loginLocation;
}

public void setLoginLocation(String loginLocation)
{
this.loginLocation = loginLocation;
}

public String getBrowser()
{
return browser;
}

public void setBrowser(String browser)
{
this.browser = browser;
}

public String getOs()
{
return os;
}

public void setOs(String os)
{
this.os = os;
}

public Long getExpireTime()
{
return expireTime;
}

public void setExpireTime(Long expireTime)
{
this.expireTime = expireTime;
}

public Set<String> getPermissions()
{
return permissions;
}

public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}

public NewsUser getUser()
{
return newsUser;
}

public void setUser(NewsUser newsUser)
{
this.newsUser = newsUser;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
}
}

auths/src/main/java/com/codezm/auths/news/domain/NewsUser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package com.codezm.auths.news.domain;

import com.ruoyi.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.util.Date;

/**
* 用户信息对象 news_user
*
* @author ruoyi
* @date 2024-03-05
*/
public class NewsUser extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 用户ID */
private Long userId;

/** 部门ID */
// @Excel(name = "部门ID")
private Long deptId;

/** 用户账号 */
// @Excel(name = "用户账号")
private String userName;

/** 用户昵称 */
// @Excel(name = "用户昵称")
private String nickName;

/** 用户类型(00系统用户) */
// @Excel(name = "用户类型", readConverterExp = "0=0系统用户")
private String userType;

/** 用户邮箱 */
// @Excel(name = "用户邮箱")
private String email;

/** 手机号码 */
// @Excel(name = "手机号码")
private String phonenumber;

/** 用户性别(0男 1女 2未知) */
// @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
private String sex;

/** 头像地址 */
// @Excel(name = "头像地址")
private String avatar;

/** 密码 */
// @Excel(name = "密码")
private String password;

/** 帐号状态(0正常 1停用) */
// @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
private String status;

/** 删除标志(0代表存在 2代表删除) */
private String delFlag;

/** 最后登录IP */
// @Excel(name = "最后登录IP")
private String loginIp;

/** 最后登录时间 */
// @JsonFormat(pattern = "yyyy-MM-dd")
// @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date loginDate;


public void setUserId(Long userId)
{
this.userId = userId;
}

public Long getUserId()
{
return userId;
}
public void setDeptId(Long deptId)
{
this.deptId = deptId;
}

public Long getDeptId()
{
return deptId;
}
public void setUserName(String userName)
{
this.userName = userName;
}

public String getUserName()
{
return userName;
}
public void setNickName(String nickName)
{
this.nickName = nickName;
}

public String getNickName()
{
return nickName;
}
public void setUserType(String userType)
{
this.userType = userType;
}

public String getUserType()
{
return userType;
}
public void setEmail(String email)
{
this.email = email;
}

public String getEmail()
{
return email;
}
public void setPhonenumber(String phonenumber)
{
this.phonenumber = phonenumber;
}

public String getPhonenumber()
{
return phonenumber;
}
public void setSex(String sex)
{
this.sex = sex;
}

public String getSex()
{
return sex;
}
public void setAvatar(String avatar)
{
this.avatar = avatar;
}
public String getAvatar()
{
return avatar;
}
public void setPassword(String password)
{
this.password = password;
}

public String getPassword()
{
return password;
}
public void setStatus(String status)
{
this.status = status;
}

public String getStatus()
{
return status;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}

public String getDelFlag()
{
return delFlag;
}
public void setLoginIp(String loginIp)
{
this.loginIp = loginIp;
}

public String getLoginIp()
{
return loginIp;
}
public void setLoginDate(Date loginDate)
{
this.loginDate = loginDate;
}

public Date getLoginDate()
{
return loginDate;
}

@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId())
.append("deptId", getDeptId())
.append("userName", getUserName())
.append("nickName", getNickName())
.append("userType", getUserType())
.append("email", getEmail())
.append("phonenumber", getPhonenumber())
.append("sex", getSex())
.append("avatar", getAvatar())
.append("password", getPassword())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())
.append("loginDate", getLoginDate())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

auths/src/main/java/com/codezm/auths/news/manager/factory/AsyncFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.codezm.auths.news.manager.factory;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.LogUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.service.ISysLogininforService;
import com.ruoyi.system.service.ISysOperLogService;
import eu.bitwalker.useragentutils.UserAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.TimerTask;

/**
* 异步工厂(产生任务用)
*
* @author ruoyi
*/
public class AsyncFactory
{
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");

/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args)
{
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
return new TimerTask()
{
@Override
public void run()
{
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus(Constants.SUCCESS);
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}

/**
* 操作日志记录
*
* @param operLog 操作日志信息
* @return 任务task
*/
public static TimerTask recordOper(final SysOperLog operLog)
{
return new TimerTask()
{
@Override
public void run()
{
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
}
};
}
}

auths/src/main/java/com/codezm/auths/news/manager/AsyncManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.codezm.auths.news.manager;

import com.ruoyi.common.utils.Threads;
import com.ruoyi.common.utils.spring.SpringUtils;

import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
* 异步任务管理器
*
* @author ruoyi
*/
public class AsyncManager
{
/**
* 操作延迟10毫秒
*/
private final int OPERATE_DELAY_TIME = 10;

/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

/**
* 单例模式
*/
private AsyncManager(){}

private static AsyncManager me = new AsyncManager();

public static AsyncManager me()
{
return me;
}

/**
* 执行任务
*
* @param task 任务
*/
public void execute(TimerTask task)
{
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}

/**
* 停止任务线程池
*/
public void shutdown()
{
Threads.shutdownAndAwaitTermination(executor);
}
}

auths/src/main/java/com/codezm/auths/news/mapper/LoginUserMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.codezm.auths.news.mapper;

import com.codezm.auths.news.domain.NewsUser;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* 用户信息Mapper接口
*
* @author ruoyi
* @date 2024-03-05
*/
@Repository
public interface LoginUserMapper
{
/**
* 查询用户信息
*
* @param userId 用户信息主键
* @return 用户信息
*/
public NewsUser selectUserByUserId(Long userId);

/**
* 查询用户信息
*
* @param userName 用户信息主键
* @return 用户信息
*/
public NewsUser selectUserByUserName(String userName);

/**
* 查询用户信息
*
* @param phone 用户信息主键
* @return 用户信息
*/
public NewsUser selectUserByPhone(String phone);

/**
* 查询用户信息列表
*
* @param newsUser 用户信息
* @return 用户信息集合
*/
public List<NewsUser> selectUserList(NewsUser newsUser);

/*
* 新增用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
public int insertUser(NewsUser newsUser);

/**
* 修改用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
public int updateUser(NewsUser newsUser);

/**
* 删除用户信息
*
* @param userId 用户信息主键
* @return 结果
*/
public int deleteUserByUserId(Long userId);

/**
* 批量删除用户信息
*
* @param userIds 需要删除的数据主键集合
* @return 结果
*/
public int deleteUserByUserIds(Long[] userIds);
}

auths/src/main/java/com/codezm/auths/news/security/context/AuthenticationContextHolder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.codezm.auths.news.security.context;

import org.springframework.security.core.Authentication;

/**
* 身份验证信息
*
* @author ruoyi
*/
public class AuthenticationContextHolder
{
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

public static Authentication getContext()
{
return contextHolder.get();
}

public static void setContext(Authentication context)
{
contextHolder.set(context);
}

public static void clearContext()
{
contextHolder.remove();
}
}

auths/src/main/java/com/codezm/auths/news/security/context/PermissionContextHolder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.codezm.auths.news.security.context;

import org.springframework.security.core.Authentication;

/**
* 身份验证信息
*
* @author ruoyi
*/
public class AuthenticationContextHolder
{
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

public static Authentication getContext()
{
return contextHolder.get();
}

public static void setContext(Authentication context)
{
contextHolder.set(context);
}

public static void clearContext()
{
contextHolder.remove();
}
}

auths/src/main/java/com/codezm/auths/news/security/filter/JwtAuthenticationTokenFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.codezm.auths.news.security.filter;

import com.codezm.auths.news.domain.NewsLoginUser;
import com.codezm.auths.news.service.TokenService;
import com.codezm.auths.news.untils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
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.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* token过滤器 验证token有效性
*
* @author ruoyi
*/
@Component("NewsJwtAuthenticationTokenFilter")
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
NewsLoginUser newsLoginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(newsLoginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(newsLoginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(newsLoginUser, null, newsLoginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}

auths/src/main/java/com/codezm/auths/news/security/handle/AuthenticationEntryPointImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.codezm.auths.news.security.handle;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
* 认证失败处理类 返回未授权
*
* @author ruoyi
*/
@Component("NewsAuthenticationEntryPointImpl")
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
private static final long serialVersionUID = -8970718410437077606L;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException
{
int code = HttpStatus.UNAUTHORIZED;
String msg = StringUtils.format("请求访问:{},认证失败,无法 访问系统资源", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
}
}

auths/src/main/java/com/codezm/auths/news/security/handle/LogoutSuccessHandlerImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.codezm.auths.news.security.handle;

import com.alibaba.fastjson2.JSON;
import com.codezm.auths.news.domain.NewsLoginUser;
import com.codezm.auths.news.manager.AsyncManager;
import com.codezm.auths.news.manager.factory.AsyncFactory;
import com.codezm.auths.news.service.TokenService;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 自定义退出处理类 返回成功
*
* @author ruoyi
*/
@Component("NewsLogoutSuccessHandlerImpl")
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
@Autowired
private TokenService tokenService;

/**
* 退出处理
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
NewsLoginUser newsLoginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(newsLoginUser))
{
String userName = newsLoginUser.getUsername();
// 删除用户缓存记录
tokenService.delLoginUser(newsLoginUser.getToken());
// 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
}
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
}
}

auths/src/main/java/com/codezm/auths/news/security/UserPwdAuthenticationProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.codezm.auths.news.security;

import org.springframework.security.authentication.AuthenticationProvider;
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.UserDetailsService;

public class UserPwdAuthenticationProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserPwdAuthenticationToken authenticationToken = (UserPwdAuthenticationToken) authentication;

String userName = (String) authenticationToken.getPrincipal();

UserDetails userDetails = userDetailsService.loadUserByUsername(userName);

// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
UserPwdAuthenticationToken authenticationResult = new UserPwdAuthenticationToken(userDetails, userDetails.getAuthorities());

authenticationResult.setDetails(authenticationToken.getDetails());

return authenticationResult;
}


@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
return UserPwdAuthenticationToken.class.isAssignableFrom(authentication);
}

public UserDetailsService getUserDetailsService() {
return userDetailsService;
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}

auths/src/main/java/com/codezm/auths/news/security/UserPwdAuthenticationToken.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.codezm.auths.news.security;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;

import java.util.Collection;


public class UserPwdAuthenticationToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

/**
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
* 在这里就代表登录的手机号码
*/
private final Object principal;

private Object credentials;

/**
* 构建一个没有鉴权的 SmsCodeAuthenticationToken
*/
public UserPwdAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}

/**
* 构建拥有鉴权的 SmsCodeAuthenticationToken
*/
public UserPwdAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}

@Override
public Object getCredentials() {
return this.credentials;
}

@Override
public Object getPrincipal() {
return this.principal;
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated,
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}

@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}

auths/src/main/java/com/codezm/auths/news/service/impl/LoginUserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.codezm.auths.news.service.impl;

import java.util.List;

import com.codezm.auths.news.domain.NewsUser;
import com.codezm.auths.news.mapper.LoginUserMapper;
import com.codezm.auths.news.service.ILoginUserService;
import com.codezm.auths.news.service.TokenService;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;



/**
* 用户信息Service业务层处理
*
* @author ruoyi
* @date 2024-03-05
*/
@Service
public class LoginUserServiceImpl implements ILoginUserService
{
@Autowired
private LoginUserMapper loginUserMapper;

@Autowired
private TokenService tokenService;

/**
* 查询用户信息
*
* @param userId 用户信息主键
* @return 用户信息
*/
@Override
public NewsUser selectUserByUserId(Long userId)
{
return loginUserMapper.selectUserByUserId(userId);
}

/**
* 查询用户信息列表
*
* @param newsUser 用户信息
* @return 用户信息
*/
@Override
public List<NewsUser> selectUserList(NewsUser newsUser)
{
return loginUserMapper.selectUserList(newsUser);
}
/**
* 通过用户名查询用户
*
* @param userName 用户名
* @return 用户对象信息
*/
@Override
public NewsUser selectUserByUserName(String userName)
{
return loginUserMapper.selectUserByUserName(userName);
}

/**
* 新增用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
@Override
public int insertUser(NewsUser newsUser)
{
newsUser.setCreateTime(DateUtils.getNowDate());
return loginUserMapper.insertUser(newsUser);
}

/**
* 修改用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
@Override
public int updateUser(NewsUser newsUser)
{
newsUser.setUpdateTime(DateUtils.getNowDate());
return loginUserMapper.updateUser(newsUser);
}

/**
* 批量删除用户信息
*
* @param userIds 需要删除的用户信息主键
* @return 结果
*/
@Override
public int deleteUserByUserIds(Long[] userIds)
{
return loginUserMapper.deleteUserByUserIds(userIds);
}

/**
* 删除用户信息信息
*
* @param userId 用户信息主键
* @return 结果
*/
@Override
public int deleteUserByUserId(Long userId)
{
return loginUserMapper.deleteUserByUserId(userId);
}
}

auths/src/main/java/com/codezm/auths/news/service/ILoginUserService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.codezm.auths.news.service.impl;

import java.util.List;

import com.codezm.auths.news.domain.NewsUser;
import com.codezm.auths.news.mapper.LoginUserMapper;
import com.codezm.auths.news.service.ILoginUserService;
import com.codezm.auths.news.service.TokenService;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;



/**
* 用户信息Service业务层处理
*
* @author ruoyi
* @date 2024-03-05
*/
@Service
public class LoginUserServiceImpl implements ILoginUserService
{
@Autowired
private LoginUserMapper loginUserMapper;

@Autowired
private TokenService tokenService;

/**
* 查询用户信息
*
* @param userId 用户信息主键
* @return 用户信息
*/
@Override
public NewsUser selectUserByUserId(Long userId)
{
return loginUserMapper.selectUserByUserId(userId);
}

/**
* 查询用户信息列表
*
* @param newsUser 用户信息
* @return 用户信息
*/
@Override
public List<NewsUser> selectUserList(NewsUser newsUser)
{
return loginUserMapper.selectUserList(newsUser);
}
/**
* 通过用户名查询用户
*
* @param userName 用户名
* @return 用户对象信息
*/
@Override
public NewsUser selectUserByUserName(String userName)
{
return loginUserMapper.selectUserByUserName(userName);
}

/**
* 新增用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
@Override
public int insertUser(NewsUser newsUser)
{
newsUser.setCreateTime(DateUtils.getNowDate());
return loginUserMapper.insertUser(newsUser);
}

/**
* 修改用户信息
*
* @param newsUser 用户信息
* @return 结果
*/
@Override
public int updateUser(NewsUser newsUser)
{
newsUser.setUpdateTime(DateUtils.getNowDate());
return loginUserMapper.updateUser(newsUser);
}

/**
* 批量删除用户信息
*
* @param userIds 需要删除的用户信息主键
* @return 结果
*/
@Override
public int deleteUserByUserIds(Long[] userIds)
{
return loginUserMapper.deleteUserByUserIds(userIds);
}

/**
* 删除用户信息信息
*
* @param userId 用户信息主键
* @return 结果
*/
@Override
public int deleteUserByUserId(Long userId)
{
return loginUserMapper.deleteUserByUserId(userId);
}
}

auths/src/main/java/com/codezm/auths/news/service/LoginService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.codezm.auths.news.service;

import com.codezm.auths.news.constant.CacheConstants;
import com.codezm.auths.news.domain.NewsUser;
import com.codezm.auths.news.domain.NewsLoginUser;
import com.codezm.auths.news.manager.AsyncManager;
import com.codezm.auths.news.manager.factory.AsyncFactory;
import com.codezm.auths.news.security.context.AuthenticationContextHolder;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.MessageUtils;

@Component("NewsLoginService")
public class LoginService {

@Autowired
@Qualifier("NewsTokenService")
private TokenService tokenService;
@Autowired
private ISysConfigService configService;
@Autowired
@Qualifier("NewsAuthenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;

@Autowired
private ILoginUserService loginUserService;

/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid)
{
// 验证码校验
validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
NewsLoginUser newsLoginUser = (NewsLoginUser) authentication.getPrincipal();
recordLoginInfo(newsLoginUser.getUserId());
// 生成token
return tokenService.createToken(newsLoginUser);
}

/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
boolean captchaEnabled = configService.selectCaptchaOnOff();
if (captchaEnabled)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
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();
}
}
}

/**
* 登录前置校验
* @param username 用户名
* @param password 用户密码
*/
public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
}

/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
NewsUser newsUser = new NewsUser();
newsUser.setUserId(userId);
newsUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
newsUser.setLoginDate(DateUtils.getNowDate());
loginUserService.updateUser(newsUser);
}

}

auths/src/main/java/com/codezm/auths/news/service/TokenService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package com.codezm.auths.news.service;

import com.codezm.auths.news.constant.CacheConstants;
import com.codezm.auths.news.domain.NewsLoginUser;


import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* token验证处理
*
* @author ruoyi
*/
@Component("NewsTokenService")
public class TokenService
{
// 令牌自定义标识
@Value("${news.token.header}")
private String header;

// 令牌秘钥
@Value("${news.token.secret}")
private String secret;

// 令牌有效期(默认30分钟)
@Value("${news.token.expireTime}")
private int expireTime;

protected static final long MILLIS_SECOND = 1000;

protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

@Autowired
private RedisCache redisCache;

/**
* 获取用户身份信息
*
* @return 用户信息
*/
public NewsLoginUser getLoginUser(HttpServletRequest request)
{
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
NewsLoginUser user = redisCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
}
}
return null;
}

/**
* 设置用户身份信息
*/
public void setLoginUser(NewsLoginUser newsLoginUser)
{
if (StringUtils.isNotNull(newsLoginUser) && StringUtils.isNotEmpty(newsLoginUser.getToken()))
{
refreshToken(newsLoginUser);
}
}

/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
}

/**
* 创建令牌
*
* @param newsLoginUser 用户信息
* @return 令牌
*/
public String createToken(NewsLoginUser newsLoginUser)
{
String token = IdUtils.fastUUID();
newsLoginUser.setToken(token);
setUserAgent(newsLoginUser);
refreshToken(newsLoginUser);

Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
}

/**
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
*
* @param newsLoginUser
* @return 令牌
*/
public void verifyToken(NewsLoginUser newsLoginUser)
{
long expireTime = newsLoginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken(newsLoginUser);
}
}

/**
* 刷新令牌有效期
*
* @param newsLoginUser 登录信息
*/
public void refreshToken(NewsLoginUser newsLoginUser)
{
newsLoginUser.setLoginTime(System.currentTimeMillis());
newsLoginUser.setExpireTime(newsLoginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(newsLoginUser.getToken());
redisCache.setCacheObject(userKey, newsLoginUser, expireTime, TimeUnit.MINUTES);
}

/**
* 设置用户代理信息
*
* @param newsLoginUser 登录信息
*/
public void setUserAgent(NewsLoginUser newsLoginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
newsLoginUser.setIpaddr(ip);
newsLoginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
newsLoginUser.setBrowser(userAgent.getBrowser().getName());
newsLoginUser.setOs(userAgent.getOperatingSystem().getName());
}

/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}

/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}

/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token);
return claims.getSubject();
}

/**
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);

if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}

private String getTokenKey(String uuid)
{
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}
}

auths/src/main/java/com/codezm/auths/news/service/UserPwdServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.codezm.auths.news.service;

import com.codezm.auths.news.domain.NewsUser;
import com.codezm.auths.news.domain.NewsLoginUser;


import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("NewsUserPwdServiceImpl")
public class UserPwdServiceImpl implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(UserPwdServiceImpl.class);

@Autowired
private ILoginUserService loginUserService;


@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
NewsUser newsUser = loginUserService.selectUserByUserName(username);
if (StringUtils.isNull(newsUser)) {
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
} else if (UserStatus.DELETED.getCode().equals(newsUser.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
} else if (UserStatus.DISABLE.getCode().equals(newsUser.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 未激活");
}

return createLoginUser(newsUser);
}

public UserDetails createLoginUser(NewsUser newsUser) {
return new NewsLoginUser(newsUser.getUserId(), newsUser.getDeptId(), newsUser);
}
}

auths/src/main/java/com/codezm/auths/news/untils/SecurityUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.codezm.auths.news.untils;

import com.codezm.auths.news.domain.NewsLoginUser;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* 安全服务工具类
*
* @author ruoyi
*/
public class SecurityUtils
{
/**
* 用户ID
**/
public static Long getUserId()
{
try
{
return getLoginUser().getUserId();
}
catch (Exception e)
{
throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
}
}

/**
* 用户OpenId
**/
public static String getOpenId()
{
try
{
return getLoginUser().getOpenId();
}
catch (Exception e)
{
throw new ServiceException("获取用户OpenId异常", HttpStatus.UNAUTHORIZED);
}
}

/**
* 获取部门ID
**/
public static Long getDeptId()
{
try
{
return getLoginUser().getDeptId();
}
catch (Exception e)
{
throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
}
}

/**
* 获取用户账户
**/
public static String getUsername()
{
try
{
return getLoginUser().getUsername();
}
catch (Exception e)
{
throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED);
}
}

/**
* 获取用户
**/
public static NewsLoginUser getLoginUser()
{
try
{
return (NewsLoginUser) getAuthentication().getPrincipal();
}
catch (Exception e)
{
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
}
}

/**
* 获取Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}

/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}

/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}

/**
* 是否为管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
}
}

/auths/src/main/resources/mapper/news/LoginUserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.codezm.auths.news.mapper.LoginUserMapper">

<resultMap type="NewsUser" id="UserResult">
<result property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="userType" column="user_type" />
<result property="email" column="email" />
<result property="phonenumber" column="phonenumber" />
<result property="sex" column="sex" />
<result property="avatar" column="avatar" />
<result property="password" column="password" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>

<sql id="selectUserVo">
select user_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark from news_user
</sql>

<select id="selectUserList" parameterType="NewsUser" resultMap="UserResult">
<include refid="selectUserVo"/>
where del_flag='0'
<if test="deptId != null "> and dept_id = #{deptId}</if>
<if test="userName != null and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
<if test="nickName != null and nickName != ''"> and nick_name like concat('%', #{nickName}, '%')</if>
<if test="userType != null and userType != ''"> and user_type = #{userType}</if>
<if test="email != null and email != ''"> and email = #{email}</if>
<if test="phonenumber != null and phonenumber != ''"> and phonenumber = #{phonenumber}</if>
<if test="sex != null and sex != ''"> and sex = #{sex}</if>
<if test="avatar != null and avatar != ''"> and avatar = #{avatar}</if>
<if test="password != null and password != ''"> and password = #{password}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="loginIp != null and loginIp != ''"> and login_ip = #{loginIp}</if>
<if test="loginDate != null "> and login_date = #{loginDate}</if>

</select>

<select id="selectUserByUserId" parameterType="Long" resultMap="UserResult">
<include refid="selectUserVo"/>
where user_id = #{userId} and del_flag = '0' limit 1
</select>

<insert id="insertUser" parameterType="NewsUser" useGeneratedKeys="true" keyProperty="userId">
insert into news_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deptId != null">dept_id,</if>
<if test="userName != null and userName != ''">user_name,</if>
<if test="nickName != null and nickName != ''">nick_name,</if>
<if test="userType != null">user_type,</if>
<if test="email != null">email,</if>
<if test="phonenumber != null">phonenumber,</if>
<if test="sex != null">sex,</if>
<if test="avatar != null">avatar,</if>
<if test="password != null">password,</if>
<if test="status != null">status,</if>
<if test="delFlag != null">del_flag,</if>
<if test="loginIp != null">login_ip,</if>
<if test="loginDate != null">login_date,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deptId != null">#{deptId},</if>
<if test="userName != null and userName != ''">#{userName},</if>
<if test="nickName != null and nickName != ''">#{nickName},</if>
<if test="userType != null">#{userType},</if>
<if test="email != null">#{email},</if>
<if test="phonenumber != null">#{phonenumber},</if>
<if test="sex != null">#{sex},</if>
<if test="avatar != null">#{avatar},</if>
<if test="password != null">#{password},</if>
<if test="status != null">#{status},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="loginIp != null">#{loginIp},</if>
<if test="loginDate != null">#{loginDate},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>

<update id="updateUser" parameterType="NewsUser">
update news_user
<trim prefix="SET" suffixOverrides=",">
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="userName != null and userName != ''">user_name = #{userName},</if>
<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
<if test="userType != null">user_type = #{userType},</if>
<if test="email != null">email = #{email},</if>
<if test="phonenumber != null">phonenumber = #{phonenumber},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="avatar != null">avatar = #{avatar},</if>
<if test="password != null">password = #{password},</if>
<if test="status != null">status = #{status},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
<if test="loginIp != null">login_ip = #{loginIp},</if>
<if test="loginDate != null">login_date = #{loginDate},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where user_id = #{userId}
</update>

<delete id="deleteUserByUserId" parameterType="Long">
update news_user set del_flag = '2' where user_id = #{userId}
</delete>

<delete id="deleteUserByUserIds" parameterType="String">
update news_user set del_flag = '2' where user_id in
<foreach item="userId" collection="array" open="(" separator="," close=")">
#{userId}
</foreach>
</delete>

<select id="selectUserByUserName" parameterType="String" resultMap="UserResult">
<include refid="selectUserVo"/>
where user_name = #{userName} and del_flag = '0' limit 1
</select>

<select id="selectUserByPhone" parameterType="String" resultMap="UserResult">
<include refid="selectUserVo"/>
where phonenumber = #{phonenumber} and del_flag = '0' limit 1
</select>
</mapper>

主 pom.xml 文件增加模块依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <dependencyManagement>
...
<dependencies>
+ <dependency>
+ <groupId>com.codezm</groupId>
+ <artifactId>auths</artifactId>
+ <version>${ruoyi.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<modules>
...
<module>ruoyi-quartz</module>
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
+ <module>auths</module>
</modules>

ruoyi-framework

增加依赖配置

ruoyi-framework/pom.xml

1
2
3
4
5
6
7
8
9
<dependencyManagement>
...
<dependencies>
+ <dependency>
+ <groupId>com.codezm</groupId>
+ <artifactId>auths</artifactId>
+ </dependency>
</dependencies>
</dependencyManagement>

ruoyi-framework 中创建基础 spring security 配置文件

ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityBaseConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ruoyi.framework.config;

import com.codezm.auths.news.config.NewsSecurityConfig;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

/**
* spring security配置
*
* @author ruoyi
*/

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import({SecurityConfig.class, NewsSecurityConfig.class})
public class SecurityBaseConfig
{

}

ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@@ -27,7 +30,9 @@ import org.springframework.web.filter.CorsFilter;
*
* @author ruoyi
*/
-@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+@Configuration
+@Order(999)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
/**
@@ -82,7 +87,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
* @return
* @throws Exception
*/
- @Bean
+ @Bean("manageAuthenticationManager")
+ @Primary
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{

增加 token 环境变量

ruoyi-admin/src/main/resources/application.yml

1
2
3
4
5
6
7
8
9
10
+# token配置
+news:
+ token:
+ # 令牌自定义标识-统一或者和前端商定
+ header: Authorization
+ # 令牌密钥
+ secret: abcdefgcodepiaonopqrstuvwxyz
+ # 令牌有效期(默认30分钟)
+ expireTime: 120
+

登录 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.codezm.news.controller;

import com.codezm.auths.news.domain.newsLoginBody;
import com.codezm.auths.news.service.LoginService;
import com.codezm.auths.news.untils.SecurityUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
* @author codezm
* @date 2024-05-14 15:56
*/

@RequestMapping("/news")
@RestController
public class UserController {

@Autowired
private LoginService loginService;

@PostMapping("/login")
public AjaxResult login(@RequestBody newsLoginBody newsLoginBody) {
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = loginService.login(newsLoginBody.getUsername(), newsLoginBody.getPassword(), newsLoginBody.getCode(), newsLoginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}

@GetMapping("/info")
public String info() {
return SecurityUtils.getUsername();
}
}

表SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE `news_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '00' COMMENT '用户类型(00系统用户)',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '手机号码',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '头像地址',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '密码',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户信息表';

INSERT INTO `ruoyi`.`sys_user`(`user_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2022-12-28 15:47:23', 'admin', '2022-12-11 16:51:52', '', '2022-12-28 15:47:22', '管理员');