spring自带security做登录认证

自定义security配置,替换掉security自带的登录认证:SecurityConfig.class
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
package com.aiidc.parse.auth.config;

import com.aiidc.parse.auth.support.AuthConfigProperties;
import com.aiidc.parse.auth.support.CustomAuthenticationFilter;
import com.aiidc.parse.auth.support.TokenFilter;
import com.aiidc.parse.auth.service.UserAuthService;
import com.aiidc.parse.core.exception.ErrorCode;
import com.aiidc.parse.core.query.CommonResponse;
import com.aiidc.parse.core.query.SecurityConstants;
import com.aiidc.parse.core.utils.JsonUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.PrintWriter;

/**
* 自定义身份验证类(用于重写WebSecurityConfigurerAdapter默认配置)
*
* @Configuration 表示这是一个配置类
* @EnableWebSecurity 允许security
* configure() 该方法重写了父类的方法,用于添加用户与角色
* ${user.auth.switch} :用户认证开关,true:开启用户认证;false:关闭用户认证. 与启动类中的
* exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}一起使用才有效果。
* 原理是:ConditionalOnProperty注解来觉得是否生成我们自定义的bean。而exclude来排除security的自动配置。
* 如果只有ConditionalOnProperty注解,而security会自动配置,那么就会使用security的自定义登录与认证;
* 而exclude来排除security的自动配置但在正常启动后还是可以正常运行,即它只是将security的自动配置关闭了而已
*/
@Configuration
@ConditionalOnProperty(name = "user.auth.switch", havingValue = "true")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)// 启用方法级别的权限认证
@EnableConfigurationProperties(AuthConfigProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AuthConfigProperties authConfigProperties;

@Autowired
private TokenFilter tokenFilter;

/**
* Http安全配置
* 表单登录:使用默认的表单登录页面和登录端点/login进行登录
* 退出登录:使用默认的退出登录端点/logout退出登录
* 记住我:使用默认的“记住我”功能,把记住用户已登录的Token保存在内存里,记住30分钟
* 权限:除了指定接口之外的其它请求都要求用户已登录
* 注意:Controller中也对URL配置了权限,如果WebSecurityConfig中和Controller中都对某文化URL配置了权限,则取较小的权限
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()//支持跨域
.disable()
.authorizeRequests()
//指定接口放行
.antMatchers(buildIgnoreUrls()).permitAll()
//OPTIONS请求全部放行
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//其他接口全部接受验证
.anyRequest().authenticated();
//使用自定义的 Token过滤器 验证请求的Token是否合法
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}

@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler((request, response, authentication) -> {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (PrintWriter writer = response.getWriter()) {
writer.write(
JsonUtil.obj2jsonString(
CommonResponse.success(authentication.getPrincipal())
)
);
}
});

filter.setAuthenticationFailureHandler((request, response, exception) -> {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (PrintWriter writer = response.getWriter()) {
writer.write(
JsonUtil.obj2jsonString(
CommonResponse.error(ErrorCode.LOGIN_AUTHORIZE_ERROR)
)
);
}
});
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}

@Bean
public TokenFilter authenticationTokenFilterBean(UserAuthService userAuthService) {
return new TokenFilter(userAuthService, SecurityConstants.HEADER_AUTH_STRING, buildIgnoreUrls());
}

private String[] buildIgnoreUrls() {
return ArrayUtils.addAll(this.authConfigProperties.getAuthIgnoreUrls(), SecurityConstants.AUTH_IGNORE_URL);
}
}
token认证与判断:TokenFilter.class
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
package com.aiidc.parse.aiidcjusticedimension.support;

import com.aiidc.parse.aiidcjusticedimension.service.UserService;
import com.aiidc.parse.aiidcjusticedimension.service.UserTokenService;
import com.aiidc.parse.aiidcparsedao.entity.User;
import com.aiidc.parse.aiidcparsedao.entity.UserToken;
import com.aiidc.parse.core.exception.AppException;
import com.aiidc.parse.core.exception.ErrorCode;
import com.aiidc.parse.core.query.CommonResponse;
import com.aiidc.parse.core.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

/**
* token验证
*
* @author: lg
* @date: 2019-07-30 10:42
*/
@Slf4j
public class TokenFilter extends OncePerRequestFilter {

private final UserTokenService tokenService;
private final UserService userService;
private final String authHeaderName;
private final String[] authIgnorePatterns;
private final AntPathMatcher pathMatcher = new AntPathMatcher();

public TokenFilter(
UserService userService,
UserTokenService tokenService,
String authHeaderName,
String[] authIgnorePatterns) {
super();
this.userService = userService;
this.tokenService = tokenService;
this.authHeaderName = authHeaderName;
this.authIgnorePatterns = authIgnorePatterns;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(authHeaderName);
if (StringUtils.isBlank(token)) {
handleAuthError(response, ErrorCode.NOT_LOGIN.errorCode(), ErrorCode.NOT_LOGIN.message());
return;
}
try {
UserToken userToken = tokenService.getByToken(token);
if (Objects.isNull(userToken)) {
throw AppException.with(ErrorCode.TOKEN_INVALID);
}
Long userId = userToken.getUserId();
User user = userService.getById(userId);
if (Objects.isNull(user)) {
throw AppException.with(ErrorCode.TOKEN_INVALID);
}

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
user, null, Arrays.asList(new SimpleGrantedAuthority("ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
//将登陆用户放到当前线程上下文中
UserContextHolder.setUser(user);
} catch (AppException e) {
handleAuthError(response, e.getErrorCode(), e.getMessage());
return;
} catch (Exception e) {
log.error("验证用户出错:token={},错误:{}", token, e);
handleAuthError(response, ErrorCode.UNAUTHORIZED.errorCode(), ErrorCode.UNAUTHORIZED.message());
return;
}
chain.doFilter(request, response);
//清除信息
log.info("clear user info........................");
UserContextHolder.remove();
}

/**
* 对鉴权异常进行统一返回
*
* @param response
* @param errorCode 错误码
* @param errorMessage 错误信息
* @throws IOException
*/
protected void handleAuthError(HttpServletResponse response,
int errorCode,
String errorMessage) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JsonUtil.obj2jsonString(CommonResponse.error(errorCode, errorMessage)));
}

/**
* 对不需要鉴权的URI直接放行
*
* @param request
* @return
* @throws ServletException
*/
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String requestURI = request.getRequestURI();
String method = request.getMethod();
for (String ignoreURI : authIgnorePatterns) {
if (pathMatcher.match(ignoreURI, requestURI)) {
log.debug("uri={}不需要鉴权,直接放行....{}", requestURI, method);
return true;
}
}
return super.shouldNotFilter(request);
}
}
TokenUtil.class
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
package com.aiidc.parse.core.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class TokenUtil {

private TokenUtil(){
}

private static final String CLAIM_KEY_USERNAME = "username";

/**
* JWT密码
*/
private static final String SECRET = "casic";

/**
* 30分钟(毫秒)
*/
private static final long EXPIRATION_TIME = 30 * 60 * 1000;

/**
* 生成一个token
* @return
*/
public static String generateToken(){
return Jwts.builder().setId(String.valueOf(UUID.randomUUID()))
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
}

/**
* 签发JWT
*/
public static String generateToken(String username) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USERNAME, username);

return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(Instant.now().toEpochMilli() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}


/**
* 检查token是否过期
* @param token
* @return true=未过期,false过期
*/
public static Boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}

/**
* 根据token获取username
*/
public static String getUsernameFromToken(String token) {
String username = getClaimsFromToken(token).get(CLAIM_KEY_USERNAME,String.class);
return username;
}

/**
* 获取token的过期时间
*/
public static Date getExpirationDateFromToken(String token) {
Date expiration = getClaimsFromToken(token).getExpiration();
return expiration;
}

/**
* 解析JWT
*/
private static Claims getClaimsFromToken(String token) {

Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
return claims;
}
}
放行接口读取:AuthConfigProperties.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.aiidc.parse.aiidcjusticedimension.support;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* 从配置文件读取不需要认证的接口
* <p>针对不同模块下的不同接口需求</p>
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "auth")
public class AuthConfigProperties {
private String[] authIgnoreUrls;
}
自定义的认证过滤器:CustomAuthenticationFilter.class
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.aiidc.parse.aiidcjusticedimension.support;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* 自定义的认证过滤器
* <p>实现POST JSON方式登陆</p>
*
* @author lg
* @since 2020-03-02
*/
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
ObjectMapper mapper = new ObjectMapper();

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}

if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
UsernamePasswordAuthenticationToken authRequest = obtainAuthToken(request).get();
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}

return super.attemptAuthentication(request, response);
}

protected Optional<UsernamePasswordAuthenticationToken> obtainAuthToken(HttpServletRequest request) {
try (InputStream in = request.getInputStream()) {
Map<String, String> requestBody = mapper.readValue(in, Map.class);
if (Objects.nonNull(requestBody)) {
return Optional.of(
new UsernamePasswordAuthenticationToken(
requestBody.get(getUsernameParameter()),
requestBody.get(getPasswordParameter()))
);
}
} catch (IOException e) {
log.error("读取登陆的用户名和密码错误:{}", e);
}

return Optional.of(
new UsernamePasswordAuthenticationToken(StringUtils.EMPTY, StringUtils.EMPTY));
}
}
UserContextHolder.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.aiidc.parse.auth.support;

import com.aiidc.parse.aiidcparsedao.entity.User;

import java.util.Optional;

public class UserContextHolder {

private static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();

public static void setUser(User user) {
USER_THREAD_LOCAL.set(user);
}

public static Optional<User> getUser() {
return Optional.ofNullable(USER_THREAD_LOCAL.get());
}

public static void remove() {
USER_THREAD_LOCAL.remove();
}
}
JsonUtil.class
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
package com.aiidc.parse.core.utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Objects;

/**
* java 基于jackson的json工具类.可以使用该类实现json字符串转java pojo,例如{@link JsonUtil#string2Obj(String, Class)},
* 反过来,也可以实现java pojo序列化为json字符串,例如{@link JsonUtil#obj2jsonString(Object)}
*
* <p>假设我们有一个{@code Artist}类</p>
* <pre>
* {@code
* class Artist {
* private String name;
* private Date birthDate;
* private LocalDate debutDate;
* //getter,setter...
* }
* }
* </pre>
* <p>
* 那么,将Artist的对象转为json字符串:
* <pre>
* {@code
* Artist art = new Artist();
* art.setName("周星驰");
* art.setBirthDate(DateUtil.toDate("1962-06-22","yyyy-MM-dd"))
* art.setDebutDate(DateUtil.toLocalDate("1980-01-01"));
*
* String jsonStr = JsonUtil.obj2jsonString(art);
*
* }
* </pre>
* 这样,我们就将一个对象转为json字符串了:
* <pre>
* {"name":"周星驰","birthDate":"1962-06-22 00:00:00","debutDate":"1980-01-01"}
* </pre>
* 这个例子中,我们也可以看出,该工具类将{@link java.util.Date}序列化的格式为{@code yyyy-MM-dd HH:mm:ss},
* 对于Java8中的{@link LocalDateTime},{@link LocalDate},{@link LocalTime}则是分别是{@code yyyy-MM-dd HH:mm:ss,yyyy-MM-dd,HH:mm:ss},
*
* <p>对于上面的json字符串,我们可以进行反序列化得到一个{@code Artist}实例</p>
* <pre>
* {@code
* Artist art1 = JsonUtil.string2Obj(jsonStr,Artist.class);
*
* System.out.println(art1.getName());//周星驰
* }
*
* </pre>
* <p>
* 工具类还提供了一个{@link JsonUtil#obj2jsonStringPretty(Object)}方法,这个方法会返回带缩进的json字符串,
* 在测试的时候很有用,输出的json格式相当美观,但是建议生产环境不要使用,因为带缩进的json相对就比较大了.
*
* <p>
* 此外,还有两个特殊的方法{@link JsonUtil#string2Obj(String, TypeReference)} 以及{@link JsonUtil#string2Obj(String, Class, Class[])}
* 这两个方法主要用于将json字符串反序列化为泛型集合时使用的,如果我们使用{@link JsonUtil#string2Obj(String, Class)}这种方式反序列化为集合类型,
* 会丢失掉类型信息.
*
* <pre>
* {@code
*
* String jsonString = [
* {"name":"许巍","birthDate":"2011-06-28 16:47:33","debutDate":"2017-08-22"},
* {"name":"张学友","birthDate":"2011-06-28 16:47:33","debutDate":"2015-03-21"},
* {"name":"华仔","birthDate":"2009-03-21 16:47:33","debutDate":"2011-03-22"}
* ]
*
* List<Artist> list1 = JsonUtil.string2Obj(jsonString, List.class);
* System.out.println(list2); //这里正常
* //list1.forEach(System.out::println); //这里就会报错了,因为实际在反序列化时存的是Map类型
*
* //对于这类反序列化,我们需要指定引用类型
* List<Artist> artistList = JsonUtil.string2Obj(jsonString,new TypeReference<List<Artist>>(){});
* list1.forEach(System.out::println); //ok
*
* //该方法等价于
* List<Artist> arts = JsonUtil.string2Obj(jsonString,List.class,Artist.class);
* arts.forEach(System.out::println); //ok
* }
*
* </pre>
* </p>
*
* @author lg
* @see ObjectMapper
*/
public class JsonUtil {

private static ObjectMapper objectMapper = new ObjectMapper();

private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class);

private JsonUtil() {
}

static {
//注册java8的一些模块
objectMapper
.registerModule(new ParameterNamesModule())//构造函数,工厂方法的支持
.registerModule(new Jdk8Module()) //Optional等类的支持
.registerModule(
new JavaTimeModule()
.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateUtil.DEFAULT_DAT_TIME_FORMATTER))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateUtil.DEFAULT_DATE_FORMATTER))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateUtil.DEFAULT_TIME_FORMATTER))
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateUtil.DEFAULT_DAT_TIME_FORMATTER))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateUtil.DEFAULT_DATE_FORMATTER))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateUtil.DEFAULT_TIME_FORMATTER))
); //java8时间的支持

// 全部字段序列化
//对象的所有字段全部列入
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.DEFAULT_DATE_TIME_PATTERN));
//忽略空Bean转json的错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//忽略 在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//map中的属性通过key值排序
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
}

/**
* 将对象转为json string
*
* @param obj
* @return
*/
public static String obj2jsonString(Object obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
LOGGER.error("对象转json string出错,", e);
return null;
}
}

/**
* 转成有格式的json字符串,这个应该用于测试环境,生产环境最好不要用,因为会产生更大的json.
*
* @param obj
* @return
*/
public static String obj2jsonStringPretty(Object obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ?
(String) obj :
objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
LOGGER.error("对象转json string出错,", e);
return null;
}
}

/**
* 字符串转对象
*
* @param jsonStr 待解析的json字符串
* @param clazz 目标类对象
* @return
*/
public static <T> T string2Obj(String jsonStr, Class<T> clazz) {
if (StringUtils.isEmpty(jsonStr) || Objects.isNull(clazz)) {
return null;
}

try {
return clazz.equals(String.class) ? (T) jsonStr : objectMapper.readValue(jsonStr, clazz);
} catch (Exception e) {
LOGGER.error("对象转json string出错,", e);
return null;
}
}


/**
* 字段符转泛型集合
*
* <pre>
* {@code
* class Artist {
* private String name;
* private Date birthDate;
* private LocalDate debutDate;
*
* //getter,setter...
* }
* String jsonString = [
* {"name":"许巍","birthDate":"2011-06-28 16:47:33","debutDate":"2017-08-22"},
* {"name":"张学友","birthDate":"2011-06-28 16:47:33","debutDate":"2015-03-21"},
* {"name":"华仔","birthDate":"2009-03-21 16:47:33","debutDate":"2011-03-22"}
* ]
*
* List<Artist> list1 = JsonUtil.string2Obj(jsonString, List.class);
* System.out.println(list2); //这里正常
* //list1.forEach(System.out::println); //这里就会报错了,因为实际在反序列化时存的是Map类型
*
* //对于这类反序列化,我们需要指定引用类型
* List<Artist> artistList = JsonUtil.string2Obj(jsonString,new TypeReference<List<Artist>>(){});
* list1.forEach(System.out::println); //ok
*
* //该方法等价于
* List<Artist> arts = JsonUtil.string2Obj(jsonString,List.class,Artist.class);
* arts.forEach(System.out::println); //ok
* }
* </pre>
*
* @param jsonStr
* @param typeReference
* @return
* @see JsonUtil#string2Obj(String, Class, Class[])
*/
public static <T> T string2Obj(String jsonStr, TypeReference typeReference) {
if (StringUtils.isEmpty(jsonStr) || typeReference == null) {
return null;
}
try {
return (T) (typeReference.getType().equals(String.class) ? jsonStr : objectMapper.readValue(jsonStr, typeReference));
} catch (Exception e) {
LOGGER.error("json字符串反序列化失败,", e);
return null;
}
}

/**
* 与{@code string2Obj(String jsonStr,TypeReference typeReference)}效果一致
* <pre>
* {@code
* List<Artist> arts = JsonUtil.string2Obj(jsonString,List.class,Artist.class);
* arts.forEach(System.out::println); //ok
* }
* </pre>
*
* @param jsonStr 待反序列化的json字符串
* @param collectionClass 集合类,例如{@code List.class}
* @param elementClasses 元素类型,例如{@code Artist.class}
* @return
* @see JsonUtil#string2Obj(String, TypeReference)
*/
public static <T> T string2Obj(String jsonStr, Class<?> collectionClass, Class<?>... elementClasses) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return objectMapper.readValue(jsonStr, javaType);
} catch (Exception e) {
LOGGER.error("json字符串反序列化失败,", e);
return null;
}
}

public static int getValueAsInt(String jsonStr, String key) throws IOException {
try {
JsonNode jsonNode = objectMapper.readTree(jsonStr);
return jsonNode.get(key).asInt();
} catch (IOException e) {
throw e;
}
}

public static String getValueAsText(String jsonStr, String key) throws IOException {
try {
JsonNode jsonNode = objectMapper.readTree(jsonStr);
return jsonNode.get(key).asText();
} catch (IOException e) {
throw e;
}
}
}
Swagger2Config.class
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
package com.aiidc.parse.core.config;

import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

@Profile("!production")
@Configuration
@EnableSwagger2
@Slf4j
public class Swagger2Config {

@Bean
public Docket restfulApi() {
ParameterBuilder tokenPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
tokenPar.name("X-Request-Token").description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false).build();
pars.add(tokenPar.build());

return new Docket(DocumentationType.SWAGGER_2)
.groupName("航天智慧-解析服务")
.genericModelSubstitutes(DeferredResult.class)
.useDefaultResponseMessages(true)
.forCodeGeneration(true)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.build()
.globalOperationParameters(pars)
.apiInfo(intoApiInfo());
}

private ApiInfo intoApiInfo() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("航天智慧-解析服务")
.description("(航天智慧-解析服务)")
.version("1.0")
.build();
return apiInfo;
}
}
相关jar与启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<jjwt.version>0.9.1</jjwt.version>

<!--使用spring自带的security做的登录认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--用于token生成与签发JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
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
package com.aiidc.parse.aiidcjusticedimension;

import com.hankcs.hanlp.utility.Predefine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
@ComponentScan("com.aiidc.parse.*")
public class AiidcJusticeDimensionApplication {

/**
* 启动时可配置外部hanlp.properties
* -Dhanlp_properties_path=
*/
public static void main(String[] args) {
//指定hanlp的配置文件路径
String hanlp_properties_path = System.getProperty("hanlp_properties_path");
System.out.println("hanlp_properties_path:" + hanlp_properties_path);
//读取设置的hanlp.properties路径
if (hanlp_properties_path != null) {
Predefine.HANLP_PROPERTIES_PATH = hanlp_properties_path;
}
SpringApplication.run(AiidcJusticeDimensionApplication.class, args);
}
}