├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── lzw
│ │ └── security
│ │ ├── SecurityApplication.java
│ │ ├── common
│ │ ├── GenericResponse.java
│ │ ├── NoPasswordEncoder.java
│ │ ├── ServiceError.java
│ │ └── WeChatUrl.java
│ │ ├── config
│ │ ├── DruidConfiguration.java
│ │ └── SpringSecurityConf.java
│ │ ├── controller
│ │ └── TestController.java
│ │ ├── entity
│ │ └── User.java
│ │ ├── filter
│ │ └── JwtAuthenticationTokenFilter.java
│ │ ├── handler
│ │ ├── AjaxAccessDeniedHandler.java
│ │ ├── AjaxAuthenticationEntryPoint.java
│ │ ├── AjaxAuthenticationFailureHandler.java
│ │ ├── AjaxAuthenticationSuccessHandler.java
│ │ └── AjaxLogoutSuccessHandler.java
│ │ ├── service
│ │ ├── RbacAuthorityService.java
│ │ ├── SelfUserDetailsService.java
│ │ ├── WeChatService.java
│ │ └── impl
│ │ │ └── WeChatServiceImpl.java
│ │ └── util
│ │ ├── AccessAddressUtil.java
│ │ ├── CollectionUtil.java
│ │ ├── DateUtil.java
│ │ ├── HttpUtil.java
│ │ ├── Jcode2SessionUtil.java
│ │ ├── JwtTokenUtil.java
│ │ └── RedisUtil.java
├── resources
│ └── application.yml
└── webapp
│ └── WEB-INF
│ └── web.xml
└── test
└── java
└── com
└── lzw
└── security
└── SecurityApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | https://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | import java.io.File;
21 | import java.io.FileInputStream;
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.net.URL;
25 | import java.nio.channels.Channels;
26 | import java.nio.channels.ReadableByteChannel;
27 | import java.util.Properties;
28 |
29 | public class MavenWrapperDownloader {
30 |
31 | /**
32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
33 | */
34 | private static final String DEFAULT_DOWNLOAD_URL =
35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
36 |
37 | /**
38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
39 | * use instead of the default one.
40 | */
41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
42 | ".mvn/wrapper/maven-wrapper.properties";
43 |
44 | /**
45 | * Path where the maven-wrapper.jar will be saved to.
46 | */
47 | private static final String MAVEN_WRAPPER_JAR_PATH =
48 | ".mvn/wrapper/maven-wrapper.jar";
49 |
50 | /**
51 | * Name of the property which should be used to override the default download url for the wrapper.
52 | */
53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
54 |
55 | public static void main(String args[]) {
56 | System.out.println("- Downloader started");
57 | File baseDirectory = new File(args[0]);
58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
59 |
60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
61 | // wrapperUrl parameter.
62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
63 | String url = DEFAULT_DOWNLOAD_URL;
64 | if (mavenWrapperPropertyFile.exists()) {
65 | FileInputStream mavenWrapperPropertyFileInputStream = null;
66 | try {
67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
68 | Properties mavenWrapperProperties = new Properties();
69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
71 | } catch (IOException e) {
72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
73 | } finally {
74 | try {
75 | if (mavenWrapperPropertyFileInputStream != null) {
76 | mavenWrapperPropertyFileInputStream.close();
77 | }
78 | } catch (IOException e) {
79 | // Ignore ...
80 | }
81 | }
82 | }
83 | System.out.println("- Downloading from: : " + url);
84 |
85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
86 | if (!outputFile.getParentFile().exists()) {
87 | if (!outputFile.getParentFile().mkdirs()) {
88 | System.out.println(
89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
90 | }
91 | }
92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
93 | try {
94 | downloadFileFromURL(url, outputFile);
95 | System.out.println("Done");
96 | System.exit(0);
97 | } catch (Throwable e) {
98 | System.out.println("- Error downloading");
99 | e.printStackTrace();
100 | System.exit(1);
101 | }
102 | }
103 |
104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
105 | URL website = new URL(urlString);
106 | ReadableByteChannel rbc;
107 | rbc = Channels.newChannel(website.openStream());
108 | FileOutputStream fos = new FileOutputStream(destination);
109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
110 | fos.close();
111 | rbc.close();
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesluozhiwei/springboot-wechat-security/fdc21b8c95be4bda85efb59c6974b932ccaf183e/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # security
2 | springboot+security+jwt+redis 实现微信小程序登录及token权限鉴定
3 | tips:这是实战篇,默认各位看官具备相应的基础(文中使用了Lombok插件,如果使用源码请先安装插件)
4 |
5 | @[TOC]
6 | # 项目配置
7 | ## 依赖
8 | ```xml
9 |
10 | org.springframework.boot
11 | spring-boot-starter-data-redis
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-security
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-web
20 |
21 |
22 | org.mybatis.spring.boot
23 | mybatis-spring-boot-starter
24 | 2.0.1
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-devtools
30 | runtime
31 | true
32 |
33 |
34 | mysql
35 | mysql-connector-java
36 | runtime
37 |
38 |
39 | org.projectlombok
40 | lombok
41 | true
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-test
46 | test
47 |
48 |
49 | org.springframework.security
50 | spring-security-test
51 | test
52 |
53 |
54 |
55 | io.jsonwebtoken
56 | jjwt
57 | 0.9.0
58 |
59 |
60 |
61 | com.alibaba
62 | fastjson
63 | 1.2.36
64 |
65 |
66 |
67 | com.alibaba
68 | druid
69 | 1.1.8
70 |
71 |
72 |
73 | log4j
74 | log4j
75 | 1.2.17
76 |
77 |
78 |
79 |
80 | org.apache.httpcomponents
81 | httpcore
82 | 4.4.11
83 |
84 |
85 |
86 | org.apache.httpcomponents
87 | httpclient
88 | 4.5.7
89 |
90 |
91 |
92 |
93 | org.bouncycastle
94 | bcprov-jdk15
95 | 1.46
96 |
97 |
98 |
99 | org.codehaus.xfire
100 | xfire-all
101 | 1.2.6
102 |
103 |
104 | org.springframework
105 | spring
106 |
107 |
108 |
109 | ```
110 | ## application.yml
111 | ```yml
112 | spring:
113 | datasource:
114 | username: root
115 | password: 123456
116 | url: jdbc:mysql://localhost:3306/db_XXX?characterEncoding=utf-8&useSSl=false
117 | driver-class-name: com.mysql.jdbc.Driver
118 | # 此处使用Druid数据库连接池
119 | type: com.alibaba.druid.pool.DruidDataSource
120 | #监控统计拦截的filters
121 | filters: stat,wall,log4j
122 | #druid配置
123 | #配置初始化大小/最小/最大
124 | initialSize: 5
125 | minIdle: 5
126 | maxActive: 20
127 | #获取连接等待超时时间
128 | maxWait: 60000
129 | #间隔多久进行一次检测,检测需要关闭的空闲连接
130 | timeBetweenEvictionRunsMillis: 60000
131 | #一个连接在池中最小生存的时间
132 | minEvictableIdleTimeMillis: 300000
133 | validationQuery: SELECT 1 FROM DUAL
134 | testWhileIdle: true
135 | testOnBorrow: false
136 | testOnReturn: false
137 | #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
138 | poolPreparedStatements: false
139 | maxPoolPreparedStatementPerConnectionSize: 20
140 | # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
141 | connectionProperties:
142 | druid:
143 | stat:
144 | mergeSql: true
145 | slowSqlMillis: 5000
146 | http:
147 | encoding:
148 | charset: utf-8
149 | force: true
150 | enabled: true
151 | redis:
152 | host: 127.0.0.1
153 | port: 6379
154 | password: 123456
155 |
156 |
157 | #mybatis是独立节点,需要单独配置
158 | mybatis:
159 | mapper-locations: classpath*:mapper/*.xml
160 | type-aliases-package: com.lzw.security.entity
161 | configuration:
162 | map-underscore-to-camel-case: true
163 |
164 | server:
165 | port: 8080
166 | tomcat:
167 | uri-encoding: utf-8
168 | servlet:
169 | context-path: /
170 |
171 | #自定义参数,可以迁移走
172 | token:
173 | #redis默认过期时间(2小时)(这是自定义的)(毫秒)
174 | expirationMilliSeconds: 7200000
175 |
176 | #微信相关参数
177 | weChat:
178 | #小程序appid
179 | appid: aaaaaaaaaaaaaaaa
180 | #小程序密钥
181 | secret: ssssssssssssssss
182 | ```
183 |
184 | # 程序代码
185 | ## security相关
186 |
187 | ### security核心配置类
188 |
189 | ```java
190 | import com.lzw.security.common.NoPasswordEncoder;
191 | import com.lzw.security.filter.JwtAuthenticationTokenFilter;
192 | import com.lzw.security.handler.*;
193 | import com.lzw.security.service.SelfUserDetailsService;
194 | import org.springframework.beans.factory.annotation.Autowired;
195 | import org.springframework.context.annotation.Bean;
196 | import org.springframework.context.annotation.Configuration;
197 | import org.springframework.http.HttpMethod;
198 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
199 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
200 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
201 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
202 | import org.springframework.security.config.core.GrantedAuthorityDefaults;
203 | import org.springframework.security.config.http.SessionCreationPolicy;
204 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
205 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
206 |
207 | /**
208 | * @author: jamesluozhiwei
209 | * @description: security核心配置类
210 | */
211 | @Configuration
212 | @EnableGlobalMethodSecurity(prePostEnabled = true)//表示开启全局方法注解,可在指定方法上面添加注解指定权限,需含有指定权限才可调用(基于表达式的权限控制)
213 | public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
214 |
215 |
216 | @Autowired
217 | AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)
218 |
219 | @Autowired
220 | AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)
221 |
222 | @Autowired
223 | AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)
224 |
225 | @Autowired
226 | AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
227 |
228 | @Autowired
229 | AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
230 |
231 | @Autowired
232 | SelfUserDetailsService userDetailsService; // 自定义user
233 |
234 | @Autowired
235 | JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
236 |
237 | @Override
238 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
239 | // 加入自定义的安全认证
240 | //auth.authenticationProvider(provider);
241 | auth.userDetailsService(userDetailsService).passwordEncoder(new NoPasswordEncoder());//这里使用自定义的加密方式(不使用加密),security提供了 BCryptPasswordEncoder 加密可自定义或使用这个
242 | }
243 |
244 | @Override
245 | protected void configure(HttpSecurity http) throws Exception {
246 | // 请根据自身业务进行扩展
247 | // 去掉 CSRF
248 | http.csrf().disable()
249 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //关闭session管理,使用token机制处理
250 | .and()
251 |
252 | .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
253 | //.and().antMatcher("/login")
254 | //.and().authorizeRequests().anyRequest().access("@rbacauthorityservice.hasPermission(request,authentication)")// 自定义权限校验 RBAC 动态 url 认证
255 | .and().authorizeRequests().antMatchers(HttpMethod.GET,"/test").hasAuthority("test:list")
256 | .and().authorizeRequests().antMatchers(HttpMethod.POST,"/test").hasAuthority("test:add")
257 | .and().authorizeRequests().antMatchers(HttpMethod.PUT,"/test").hasAuthority("test:update")
258 | .and().authorizeRequests().antMatchers(HttpMethod.DELETE,"/test").hasAuthority("test:delete")
259 | .and().authorizeRequests().antMatchers("/test/*").hasAuthority("test:manager")
260 | .and().authorizeRequests().antMatchers("/login").permitAll() //放行login(这里使用自定义登录)
261 | .and().authorizeRequests().antMatchers("/hello").permitAll();//permitAll表示不需要认证
262 | //微信小程序登录不给予账号密码,关闭
263 | // .and()
264 | //开启登录, 定义当需要用户登录时候,转到的登录页面、这是使用security提供的formLogin,不需要自己实现登录登出逻辑、但需要实现相关方法
265 | // .formLogin()
266 | // .loginPage("/test/login.html")//可不指定,使用security自带的登录页面
267 | // .loginProcessingUrl("/login") //登录地址
268 | // .successHandler(authenticationSuccessHandler) // 登录成功处理
269 | // .failureHandler(authenticationFailureHandler) // 登录失败处理
270 | // .permitAll()
271 |
272 | // .and()
273 | // .logout()//默认注销行为为logout
274 | // .logoutUrl("/logout")
275 | // .logoutSuccessHandler(logoutSuccessHandler)
276 | // .permitAll();
277 |
278 | // 记住我
279 | // http.rememberMe().rememberMeParameter("remember-me")
280 | // .userDetailsService(userDetailsService).tokenValiditySeconds(1000);
281 |
282 | http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
283 | http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
284 |
285 | }
286 |
287 | @Bean
288 | GrantedAuthorityDefaults grantedAuthorityDefaults(){
289 | return new GrantedAuthorityDefaults("");//remove the ROLE_ prefix
290 | }
291 |
292 | }
293 | ```
294 | 注意:这里说明一下hasRole("ADMIN")和hasAuthority("ADMIN")的区别,在鉴权的时候,hasRole会给 "ADMIN" 加上 ROLE_ 变成 "ROLE_ADMIN" 而hasAuthority则不会 还是 "ADMIN"、如果不想让其添加前缀,可以使用如下代码移除
295 |
296 | ```java
297 | //在上面也有体现
298 | @Bean
299 | GrantedAuthorityDefaults grantedAuthorityDefaults(){
300 | return new GrantedAuthorityDefaults("");//remove the ROLE_ prefix
301 | }
302 | ```
303 | ### 鉴权各种情况处理类
304 | 上述代码引用的鉴权状态处理代码
305 |
306 | #### 无权访问
307 | ```java
308 | /**
309 | * @author: jamesluozhiwei
310 | * @description: 无权访问
311 | */
312 | @Component
313 | public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
314 | @Override
315 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
316 | response.setCharacterEncoding("utf-8");
317 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_AUTHORITY)));
318 | }
319 | }
320 | ```
321 | #### 用户未登录时返回给前端的数据
322 | ```java
323 | /**
324 | * @author: jamesluozhiwei
325 | * @description: 用户未登录时返回给前端的数据
326 | */
327 | @Component
328 | public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
329 |
330 | @Override
331 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
332 | request.setCharacterEncoding("utf-8");
333 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
334 | }
335 | }
336 | ```
337 | #### 用户登录失败时返回给前端的数据(本程序未使用)
338 | 适用于账号密码登录模式
339 | ```java
340 | /**
341 | * @author: jamesluozhiwei
342 | * @description: 用户登录失败时返回给前端的数据
343 | */
344 | @Component
345 | public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
346 |
347 | @Override
348 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
349 | response.setCharacterEncoding("utf-8");
350 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_CODE)));
351 | }
352 |
353 | }
354 | ```
355 | #### 用户登录成功时返回给前端的数据
356 | 适用于账号密码登录模式
357 | ```java
358 | /**
359 | * @author: jamesluozhiwei
360 | * @description: 用户登录成功时返回给前端的数据
361 | */
362 | @Component
363 | @Slf4j
364 | public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
365 |
366 | @Autowired
367 | private RedisUtil redisUtil;
368 |
369 | @Override
370 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
371 | //自定义login,不走这里、若使用security的formLogin则自己添加业务实现(生成token、存储token等等)
372 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.NORMAL)));
373 | }
374 | }
375 | ```
376 | #### 登出成功
377 | 适用于账号密码登录模式
378 | ```java
379 | /**
380 | * @author: jamesluozhiwei
381 | * @description: 登出成功
382 | */
383 | @Component
384 | @Slf4j
385 | public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
386 |
387 | @Autowired
388 | private RedisUtil redisUtil;
389 |
390 | @Override
391 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
392 | //没有logout不走这里、若使用security的formLogin则自己添加业务实现(移除token等等)
393 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.NORMAL)));
394 | }
395 |
396 | }
397 | ```
398 |
399 | ### JWT自定义过滤器
400 | 在security配置类中有体现,主要用于解析token,并从redis中获取用户相关权限
401 | ```java
402 | import com.alibaba.fastjson.JSON;
403 | import com.lzw.security.common.GenericResponse;
404 | import com.lzw.security.common.ServiceError;
405 | import com.lzw.security.entity.User;
406 | import com.lzw.security.util.*;
407 | import lombok.extern.slf4j.Slf4j;
408 | import org.springframework.beans.factory.annotation.Autowired;
409 | import org.springframework.beans.factory.annotation.Value;
410 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
411 | import org.springframework.security.core.GrantedAuthority;
412 | import org.springframework.security.core.context.SecurityContextHolder;
413 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
414 | import org.springframework.stereotype.Component;
415 | import org.springframework.web.filter.OncePerRequestFilter;
416 |
417 | import javax.servlet.FilterChain;
418 | import javax.servlet.ServletException;
419 | import javax.servlet.http.HttpServletRequest;
420 | import javax.servlet.http.HttpServletResponse;
421 | import java.io.IOException;
422 | import java.util.HashMap;
423 | import java.util.Set;
424 |
425 | /**
426 | * @author: jamesluozhiwei
427 | * @description: 确保在一次请求只通过一次filter,而不需要重复执行
428 | */
429 | @Component
430 | @Slf4j
431 | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
432 |
433 | @Value("${token.expirationMilliSeconds}")
434 | private long expirationMilliSeconds;
435 |
436 | @Autowired
437 | RedisUtil redisUtil;
438 |
439 | @Override
440 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
441 | //获取header中的token信息
442 | String authHeader = request.getHeader("Authorization");
443 | response.setCharacterEncoding("utf-8");
444 | if (null == authHeader || !authHeader.startsWith("Bearer ")){
445 | filterChain.doFilter(request,response);//token格式不正确
446 | return;
447 | }
448 | String authToken = authHeader.substring("Bearer ".length());
449 |
450 | String subject = JwtTokenUtil.parseToken(authToken);//获取在token中自定义的subject,用作用户标识,用来获取用户权限
451 |
452 | //获取redis中的token信息
453 |
454 | if (!redisUtil.hasKey(authToken)){
455 | //token 不存在 返回错误信息
456 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
457 | return;
458 | }
459 |
460 | //获取缓存中的信息(根据自己的业务进行拓展)
461 | HashMap hashMap = (HashMap) redisUtil.hget(authToken);
462 | //从tokenInfo中取出用户信息
463 | User user = new User();
464 | user.setId(Long.parseLong(hashMap.get("id").toString())).setAuthorities((Set extends GrantedAuthority>) hashMap.get("authorities"));
465 | if (null == hashMap){
466 | //用户信息不存在或转换错误,返回错误信息
467 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
468 | return;
469 | }
470 | //更新token过期时间
471 | redisUtil.setKeyExpire(authToken,expirationMilliSeconds);
472 | //将信息交给security
473 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
474 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
475 | SecurityContextHolder.getContext().setAuthentication(authenticationToken);
476 | filterChain.doFilter(request,response);
477 | }
478 | }
479 | ```
480 | ### SelfUserDetailsService(基于自定义登录,token验证可忽略)
481 | ```java
482 | package com.lzw.security.service;
483 | import com.lzw.security.entity.User;
484 | import lombok.extern.slf4j.Slf4j;
485 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
486 | import org.springframework.security.core.userdetails.UserDetails;
487 | import org.springframework.security.core.userdetails.UserDetailsService;
488 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
489 | import org.springframework.stereotype.Component;
490 |
491 | import java.util.HashSet;
492 | import java.util.Set;
493 |
494 | /**
495 | * 用户认证、权限、使用security的表单登录时会被调用(自定义登录请忽略)
496 | * @author: jamesluozhiwei
497 | */
498 | @Component
499 | @Slf4j
500 | public class SelfUserDetailsService implements UserDetailsService {
501 |
502 | //@Autowired
503 | //private UserMapper userMapper;
504 |
505 | /**
506 | * 若使用security表单鉴权则需实现该方法,通过username获取用户信息(密码、权限等等)
507 | * @param username
508 | * @return
509 | * @throws UsernameNotFoundException
510 | */
511 | @Override
512 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
513 |
514 | //通过username查询用户
515 | //根据自己的业务获取用户信息
516 | //SelfUserDetails user = userMapper.getUser(username);
517 | //模拟从数据库获取到用户信息
518 | User user = new User();
519 | if(user == null){
520 | //仍需要细化处理
521 | throw new UsernameNotFoundException("该用户不存在");
522 | }
523 |
524 | Set authoritiesSet = new HashSet();
525 | // 模拟从数据库中获取用户权限
526 | authoritiesSet.add(new SimpleGrantedAuthority("test:list"));
527 | authoritiesSet.add(new SimpleGrantedAuthority("test:add"));
528 | user.setAuthorities(authoritiesSet);
529 |
530 | log.info("用户{}验证通过",username);
531 | return user;
532 | }
533 | }
534 | ```
535 | ### 密码加密方式
536 | 这里就不用加密了
537 | ```java
538 | import org.springframework.security.crypto.password.PasswordEncoder;
539 |
540 | public class NoPasswordEncoder implements PasswordEncoder {
541 | @Override
542 | public String encode(CharSequence charSequence) {
543 | return "";
544 | }
545 |
546 | @Override
547 | public boolean matches(CharSequence charSequence, String s) {
548 | return true;
549 | }
550 | }
551 |
552 | ```
553 | ### RBAC自定义鉴权
554 | 可在security配置中,通过
555 | ```java
556 | .and().authorizeRequests().anyRequest().access("@rbacauthorityservice.hasPermission(request,authentication)")//anyRequest表示全部
557 | .and().authorizeRequests().antMatchers("/test/*").access("@rbacauthorityservice.hasPermission(request,authentication)")//也可以指定相应的地址
558 | ```
559 | 指定自定义鉴权方式,也可指定具体的URL
560 | ```java
561 | /**
562 | * 鉴权处理
563 | */
564 | @Component("rbacauthorityservice")//此处bean名称要和上述的一致
565 | public class RbacAuthorityService {
566 | /**
567 | * 可根据业务自定义鉴权
568 | * @param request
569 | * @param authentication 用户权限信息
570 | * @return 通过返回true 不通过则返回false(所有鉴权只要有一个通过了则为通过)
571 | */
572 | public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
573 | Object userInfo = authentication.getPrincipal();
574 |
575 | boolean hasPermission = false;
576 |
577 | if (userInfo instanceof UserDetails) {
578 |
579 | String username = ((UserDetails) userInfo).getUsername();
580 |
581 | //获取资源
582 | Set urls = new HashSet();
583 | // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
584 | // 模拟鉴权(可根据自己的业务扩展)
585 | urls.add("/demo/**");//application.yml里设置了项目路径,百度一下我就不贴了
586 | Set set2 = new HashSet();
587 | Set set3 = new HashSet();
588 |
589 | AntPathMatcher antPathMatcher = new AntPathMatcher();
590 |
591 | for (String url : urls) {
592 | if (antPathMatcher.match(url, request.getRequestURI())) {
593 | hasPermission = true;
594 | break;
595 | }
596 | }
597 | return hasPermission;
598 | } else {
599 | return false;
600 | }
601 | }
602 | }
603 | ```
604 | ## 微信小程序相关
605 | ### 通过code换取openid
606 | ```java
607 | import com.alibaba.fastjson.JSONObject;
608 | import com.lzw.security.common.WeChatUrl;
609 | import lombok.extern.slf4j.Slf4j;
610 | import org.apache.http.NameValuePair;
611 | import org.apache.http.client.entity.UrlEncodedFormEntity;
612 | import org.apache.http.client.methods.HttpPost;
613 | import org.apache.http.impl.client.DefaultHttpClient;
614 | import org.apache.http.message.BasicNameValuePair;
615 | import org.apache.http.util.EntityUtils;
616 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
617 | import org.codehaus.xfire.util.Base64;
618 |
619 | import javax.crypto.Cipher;
620 | import javax.crypto.spec.IvParameterSpec;
621 | import javax.crypto.spec.SecretKeySpec;
622 | import java.security.AlgorithmParameters;
623 | import java.security.Security;
624 | import java.util.ArrayList;
625 | import java.util.Arrays;
626 | import java.util.List;
627 |
628 | @Slf4j
629 | public class Jcode2SessionUtil {
630 |
631 | /**
632 | * 请求微信后台获取用户数据
633 | * @param code wx.login获取到的临时code
634 | * @return 请求结果
635 | * @throws Exception
636 | */
637 | public static String jscode2session(String appid,String secret,String code,String grantType)throws Exception{
638 | //定义返回的json对象
639 | JSONObject result = new JSONObject();
640 | //创建请求通过code换取session等数据
641 | HttpPost httpPost = new HttpPost(WeChatUrl.JS_CODE_2_SESSION.getUrl());
642 | List params=new ArrayList();
643 | //建立一个NameValuePair数组,用于存储欲传送的参数
644 | params.add(new BasicNameValuePair("appid",appid));
645 | params.add(new BasicNameValuePair("secret",secret));
646 | params.add(new BasicNameValuePair("js_code",code));
647 | params.add(new BasicNameValuePair("grant_type",grantType));
648 | //设置编码
649 | httpPost.setEntity(new UrlEncodedFormEntity(params));//添加参数
650 | return EntityUtils.toString(new DefaultHttpClient().execute(httpPost).getEntity());
651 | }
652 | /**
653 | * 解密用户敏感数据获取用户信息
654 | * @param sessionKey 数据进行加密签名的密钥
655 | * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
656 | * @param iv 加密算法的初始向量
657 | * @return
658 | */
659 | public static String getUserInfo(String encryptedData,String sessionKey,String iv)throws Exception{
660 | // 被加密的数据
661 | byte[] dataByte = Base64.decode(encryptedData);
662 | // 加密秘钥
663 | byte[] keyByte = Base64.decode(sessionKey);
664 | // 偏移量
665 | byte[] ivByte = Base64.decode(iv);
666 | // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
667 | int base = 16;
668 | if (keyByte.length % base != 0) {
669 | int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
670 | byte[] temp = new byte[groups * base];
671 | Arrays.fill(temp, (byte) 0);
672 | System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
673 | keyByte = temp;
674 | }
675 | // 初始化
676 | Security.addProvider(new BouncyCastleProvider());
677 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
678 | SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
679 | AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
680 | parameters.init(new IvParameterSpec(ivByte));
681 | cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
682 | byte[] resultByte = cipher.doFinal(dataByte);
683 | if (null != resultByte && resultByte.length > 0) {
684 | String result = new String(resultByte, "UTF-8");
685 | log.info(result);
686 | return result;
687 | }
688 | return null;
689 | }
690 |
691 | /**
692 | * 获取微信接口调用凭证
693 | * @param appid
694 | * @param secret
695 | * @return 返回String 可转JSON
696 | */
697 | public static String getAccessToken(String appid,String secret){
698 | JSONObject params = new JSONObject();
699 | params.put("grant_type","client_credential");//获取接口调用凭证
700 | params.put("appid",appid);
701 | params.put("secret",secret);
702 | return HttpUtil.sendGet(WeChatUrl.GET_ACCESS_TOKEN.getUrl()+"?grant_type=client_credential&appid=" + appid + "&secret=" + secret);
703 | }
704 |
705 | /**
706 | * 发送模板消息
707 | * @param access_token 接口调用凭证
708 | * @param touser 接收者(用户)的 openid
709 | * @param template_id 所需下发的模板消息id
710 | * @param page 点击模版卡片后跳转的页面,仅限本小程序内的页面。支持带参数,(eg:index?foo=bar)。该字段不填则模版无法跳转
711 | * @param form_id 表单提交场景下,为submit事件带上的formId;支付场景下,为本次支付的 prepay_id
712 | * @param data 模版内容,不填则下发空模版。具体格式请参照官网示例
713 | * @param emphasis_keyword 模版需要放大的关键词,不填则默认无放大
714 | * @return 返回String可转JSON
715 | */
716 | public static String sendTemplateMessage(String access_token,String touser,String template_id,String page,String form_id,Object data,String emphasis_keyword){
717 | JSONObject params = new JSONObject();
718 | params.put("touser",touser);
719 | params.put("template_id",template_id);
720 | if (null != page && !"".equals(page)){
721 | params.put("page",page);
722 | }
723 | params.put("form_id",form_id);
724 | params.put("data",data);
725 | if (null != emphasis_keyword && !"".equals(emphasis_keyword)){
726 | params.put("emphasis_keyword",emphasis_keyword);
727 | }
728 | //发送请求
729 | return HttpUtil.sendPost(WeChatUrl.SEND_TEMPLATE_MESSAGE.getUrl() + "?access_token=" + access_token,params.toString());
730 | }
731 |
732 | }
733 | ```
734 | 请求地址枚举,可自行扩展
735 | ```java
736 | public enum WeChatUrl {
737 |
738 | JS_CODE_2_SESSION("https://api.weixin.qq.com/sns/jscode2session")
739 | ,GET_ACCESS_TOKEN("https://api.weixin.qq.com/cgi-bin/token")
740 | ,SEND_TEMPLATE_MESSAGE("https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send")
741 | ;
742 |
743 | private String url;
744 |
745 | WeChatUrl() {
746 | }
747 |
748 | WeChatUrl(String url) {
749 | this.url = url;
750 | }
751 |
752 | public String getUrl() {
753 | return url;
754 | }
755 |
756 | public WeChatUrl setUrl(String url) {
757 | this.url = url;
758 | return this;
759 | }
760 | }
761 | ```
762 | http工具类
763 | ```java
764 | import com.alibaba.fastjson.JSONObject;
765 | import org.apache.http.HttpEntity;
766 | import org.apache.http.HttpResponse;
767 | import org.apache.http.client.methods.HttpGet;
768 | import org.apache.http.client.methods.HttpPost;
769 | import org.apache.http.entity.StringEntity;
770 | import org.apache.http.impl.client.DefaultHttpClient;
771 | import org.apache.http.util.EntityUtils;
772 | import java.io.*;
773 | import java.net.HttpURLConnection;
774 | import java.net.URL;
775 | import java.util.Base64;
776 | /**
777 | * 请求工具类
778 | * @author jamesluozhiwei
779 | */
780 | public class HttpUtil {
781 |
782 | /**
783 | * 发送get请求
784 | * @param url
785 | * @return
786 | */
787 | public static String sendGet(String url){
788 | DefaultHttpClient httpClient = new DefaultHttpClient();
789 | HttpGet httpGet = new HttpGet(url);
790 | String result = null;
791 | try {
792 | HttpResponse response = httpClient.execute(httpGet);
793 | HttpEntity entity = response.getEntity();
794 | if (entity != null) {
795 | result = EntityUtils.toString(entity, "UTF-8");
796 | }
797 | httpGet.releaseConnection();
798 | } catch (IOException e) {
799 | e.printStackTrace();
800 | }
801 | return result;
802 | }
803 |
804 |
805 | /**
806 | * 发送post请求
807 | * @param url
808 | * @param params 可使用JSONObject转JSON字符串
809 | * @return
810 | */
811 | public static String sendPost(String url,String params){
812 | DefaultHttpClient httpClient = new DefaultHttpClient();
813 | HttpPost httpPost = new HttpPost(url);
814 | JSONObject jsonObject = null;
815 | try {
816 | httpPost.setEntity(new StringEntity(params, "UTF-8"));
817 | HttpResponse response = httpClient.execute(httpPost);
818 | return EntityUtils.toString(response.getEntity(),"UTF-8");
819 | } catch (IOException e) {
820 | e.printStackTrace();
821 | }
822 | return null;
823 | }
824 |
825 | /**
826 | * 发送post请求
827 | * @param httpUrl
828 | * @param param JSON字符串
829 | * @return
830 | */
831 | public static String doPostBase64(String httpUrl, String param) {
832 |
833 | HttpURLConnection connection = null;
834 | InputStream is = null;
835 | OutputStream os = null;
836 | BufferedReader br = null;
837 | String result = null;
838 | try {
839 | URL url = new URL(httpUrl);
840 | // 通过远程url连接对象打开连接
841 | connection = (HttpURLConnection) url.openConnection();
842 | // 设置连接请求方式
843 | connection.setRequestMethod("POST");
844 | // 设置连接主机服务器超时时间:15000毫秒
845 | connection.setConnectTimeout(15000);
846 | // 设置读取主机服务器返回数据超时时间:60000毫秒
847 | connection.setReadTimeout(60000);
848 |
849 | // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
850 | connection.setDoOutput(true);
851 | // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
852 | connection.setDoInput(true);
853 | // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。
854 | connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
855 | // 通过连接对象获取一个输出流
856 | os = connection.getOutputStream();
857 | // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
858 |
859 | os.write(param.getBytes());
860 |
861 | // 通过连接对象获取一个输入流,向远程读取
862 | if (connection.getResponseCode() == 200) {
863 |
864 | is = connection.getInputStream();
865 |
866 | ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
867 | byte[] buff = new byte[100];
868 | int rc = 0;
869 | while ((rc = is.read(buff, 0, 100)) > 0) {
870 | swapStream.write(buff, 0, rc);
871 | }
872 | byte[] in2b = swapStream.toByteArray();
873 | String tmp = new String(in2b);
874 | if (tmp.indexOf("errcode") == -1)
875 | return Base64.getEncoder().encodeToString(in2b);
876 | return tmp;
877 | }
878 | } catch (Exception e) {
879 | e.printStackTrace();
880 | } finally {
881 | // 关闭资源
882 | if (null != br) {
883 | try {
884 | br.close();
885 | } catch (IOException e) {
886 | e.printStackTrace();
887 | }
888 | }
889 | if (null != os) {
890 | try {
891 | os.close();
892 | } catch (IOException e) {
893 | e.printStackTrace();
894 | }
895 | }
896 | if (null != is) {
897 | try {
898 | is.close();
899 | } catch (IOException e) {
900 | e.printStackTrace();
901 | }
902 | }
903 | // 断开与远程地址url的连接
904 | connection.disconnect();
905 | }
906 | return result;
907 | }
908 | }
909 | ```
910 | ### 业务层
911 | ```java
912 | /**
913 | * 微信业务接口
914 | */
915 | public interface WeChatService {
916 |
917 | /**
918 | * 小程序登录
919 | * @param code
920 | * @return
921 | */
922 | GenericResponse wxLogin(String code)throws Exception;
923 |
924 | }
925 | ```
926 | ```java
927 | import com.alibaba.fastjson.JSONObject;
928 | import com.lzw.security.common.GenericResponse;
929 | import com.lzw.security.common.ServiceError;
930 | import com.lzw.security.entity.User;
931 | import com.lzw.security.service.WeChatService;
932 | import com.lzw.security.util.Jcode2SessionUtil;
933 | import com.lzw.security.util.JwtTokenUtil;
934 | import com.lzw.security.util.RedisUtil;
935 | import lombok.extern.slf4j.Slf4j;
936 | import org.springframework.beans.factory.annotation.Autowired;
937 | import org.springframework.beans.factory.annotation.Value;
938 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
939 | import org.springframework.stereotype.Service;
940 | import org.springframework.util.Assert;
941 | import java.util.HashMap;
942 | import java.util.HashSet;
943 | import java.util.Set;
944 |
945 | /**
946 | * 微信业务实现类
947 | */
948 | @Service
949 | @Slf4j
950 | public class WeChatServiceImpl implements WeChatService {
951 |
952 | @Value("${weChat.appid}")
953 | private String appid;
954 |
955 | @Value("${weChat.secret}")
956 | private String secret;
957 |
958 | @Autowired
959 | private RedisUtil redisUtil;
960 |
961 | @Override
962 | public GenericResponse wxLogin(String code) throws Exception{
963 | JSONObject sessionInfo = JSONObject.parseObject(jcode2Session(code));
964 |
965 | Assert.notNull(sessionInfo,"code 无效");
966 |
967 | Assert.isTrue(0 == sessionInfo.getInteger("errcode"),sessionInfo.getString("errmsg"));
968 |
969 | // 获取用户唯一标识符 openid成功
970 | // 模拟从数据库获取用户信息
971 | User user = new User();
972 | user.setId(1L);
973 | Set authoritiesSet = new HashSet();
974 | // 模拟从数据库中获取用户权限
975 | authoritiesSet.add(new SimpleGrantedAuthority("test:add"));
976 | authoritiesSet.add(new SimpleGrantedAuthority("test:list"));
977 | authoritiesSet.add(new SimpleGrantedAuthority("ddd:list"));
978 | user.setAuthorities(authoritiesSet);
979 | HashMap hashMap = new HashMap<>();
980 | hashMap.put("id",user.getId().toString());
981 | hashMap.put("authorities",authoritiesSet);
982 | String token = JwtTokenUtil.generateToken(user);
983 | redisUtil.hset(token,hashMap);
984 |
985 | return GenericResponse.response(ServiceError.NORMAL,token);
986 | }
987 |
988 | /**
989 | * 登录凭证校验
990 | * @param code
991 | * @return
992 | * @throws Exception
993 | */
994 | private String jcode2Session(String code)throws Exception{
995 | String sessionInfo = Jcode2SessionUtil.jscode2session(appid,secret,code,"authorization_code");//登录grantType固定
996 | log.info(sessionInfo);
997 | return sessionInfo;
998 | }
999 | }
1000 | ```
1001 | ### 控制层
1002 | ```java
1003 | import com.lzw.security.common.GenericResponse;
1004 | import com.lzw.security.common.ServiceError;
1005 | import com.lzw.security.service.WeChatService;
1006 | import org.springframework.beans.factory.annotation.Autowired;
1007 | import org.springframework.security.access.prepost.PreAuthorize;
1008 | import org.springframework.web.bind.annotation.GetMapping;
1009 | import org.springframework.web.bind.annotation.PostMapping;
1010 | import org.springframework.web.bind.annotation.RestController;
1011 |
1012 | @RestController
1013 | public class TestController {
1014 |
1015 | @Autowired
1016 | private WeChatService weChatService;
1017 |
1018 | /**
1019 | * code登录获取用户openid
1020 | * @param code
1021 | * @return
1022 | * @throws Exception
1023 | */
1024 | @PostMapping("/login")
1025 | public GenericResponse login(String code)throws Exception{
1026 | return weChatService.wxLogin(code);
1027 | }
1028 |
1029 | /**
1030 | * 权限测试
1031 | */
1032 |
1033 | @GetMapping("/test")
1034 | public GenericResponse test(){
1035 | return GenericResponse.response(ServiceError.NORMAL,"test");
1036 | }
1037 |
1038 | @PostMapping("/test")
1039 | public GenericResponse testPost(){
1040 | return GenericResponse.response(ServiceError.NORMAL,"testPOST");
1041 | }
1042 |
1043 | @GetMapping("/test/a")
1044 | public GenericResponse testA(){
1045 | return GenericResponse.response(ServiceError.NORMAL,"testManage");
1046 | }
1047 |
1048 | @GetMapping("/hello")
1049 | public GenericResponse hello(){
1050 | return GenericResponse.response(ServiceError.NORMAL,"hello security");
1051 | }
1052 |
1053 | @GetMapping("/ddd")
1054 | @PreAuthorize("hasAuthority('ddd:list')")//基于表达式的权限验证,调用此方法需有 "ddd:list" 的权限
1055 | public GenericResponse ddd(){
1056 | return GenericResponse.response(ServiceError.NORMAL,"dddList");
1057 | }
1058 |
1059 | @PostMapping("/ddd")
1060 | @PreAuthorize("hasAuthority('ddd:add')")//基于表达式的权限验证,调用此方法需有 "ddd:list" 的权限
1061 | public GenericResponse dddd(){
1062 | return GenericResponse.response(ServiceError.NORMAL,"testPOST");
1063 | }
1064 | }
1065 | ```
1066 | ## 工具类相关
1067 | ### redis
1068 | ```java
1069 | import org.springframework.beans.factory.annotation.Autowired;
1070 | import org.springframework.beans.factory.annotation.Value;
1071 | import org.springframework.data.redis.core.RedisTemplate;
1072 | import org.springframework.stereotype.Component;
1073 | import java.util.HashMap;
1074 | import java.util.Set;
1075 | import java.util.concurrent.TimeUnit;
1076 | /**
1077 | * redis工具类
1078 | * @author: jamesluozhiwei
1079 | */
1080 | @Component
1081 | public class RedisUtil {
1082 |
1083 | @Value("${token.expirationMilliSeconds}")
1084 | private long expirationMilliSeconds;
1085 |
1086 | //@Autowired
1087 | //private StringRedisTemplate redisTemplate;
1088 |
1089 | @Autowired
1090 | private RedisTemplate redisTemplate;
1091 |
1092 | /**
1093 | * 查询key,支持模糊查询
1094 | * @param key
1095 | * */
1096 | public Set keys(String key){
1097 | return redisTemplate.keys(key);
1098 | }
1099 |
1100 | /**
1101 | * 字符串获取值
1102 | * @param key
1103 | * */
1104 | public Object get(String key){
1105 | return redisTemplate.opsForValue().get(key);
1106 | }
1107 |
1108 | /**
1109 | * 字符串存入值
1110 | * 默认过期时间为2小时
1111 | * @param key
1112 | * */
1113 | public void set(String key, String value){
1114 | set(key,value,expirationMilliSeconds);
1115 | }
1116 |
1117 | /**
1118 | * 字符串存入值
1119 | * @param expire 过期时间(毫秒计)
1120 | * @param key
1121 | * */
1122 | public void set(String key, String value,long expire){
1123 | redisTemplate.opsForValue().set(key,value, expire,TimeUnit.MILLISECONDS);
1124 | }
1125 |
1126 | /**
1127 | * 删出key
1128 | * 这里跟下边deleteKey()最底层实现都是一样的,应该可以通用
1129 | * @param key
1130 | * */
1131 | public void delete(String key){
1132 | redisTemplate.opsForValue().getOperations().delete(key);
1133 | }
1134 |
1135 | /**
1136 | * 添加单个
1137 | * @param key key
1138 | * @param filed filed
1139 | * @param domain 对象
1140 | */
1141 | public void hset(String key,String filed,Object domain){
1142 | hset(key,filed,domain,expirationMilliSeconds);
1143 | }
1144 |
1145 | /**
1146 | * 添加单个
1147 | * @param key key
1148 | * @param filed filed
1149 | * @param domain 对象
1150 | * @param expire 过期时间(毫秒计)
1151 | */
1152 | public void hset(String key,String filed,Object domain,long expire){
1153 | redisTemplate.opsForHash().put(key, filed, domain);
1154 | setKeyExpire(key,expirationMilliSeconds);
1155 | }
1156 |
1157 | /**
1158 | * 添加HashMap
1159 | *
1160 | * @param key key
1161 | * @param hm 要存入的hash表
1162 | */
1163 | public void hset(String key, HashMap hm){
1164 | redisTemplate.opsForHash().putAll(key,hm);
1165 | setKeyExpire(key,expirationMilliSeconds);
1166 | }
1167 |
1168 | /**
1169 | * 如果key存在就不覆盖
1170 | * @param key
1171 | * @param filed
1172 | * @param domain
1173 | */
1174 | public void hsetAbsent(String key,String filed,Object domain){
1175 | redisTemplate.opsForHash().putIfAbsent(key, filed, domain);
1176 | }
1177 |
1178 | /**
1179 | * 查询key和field所确定的值
1180 | * @param key 查询的key
1181 | * @param field 查询的field
1182 | * @return HV
1183 | */
1184 | public Object hget(String key,String field) {
1185 | return redisTemplate.opsForHash().get(key, field);
1186 | }
1187 |
1188 | /**
1189 | * 查询该key下所有值
1190 | * @param key 查询的key
1191 | * @return Map
1192 | */
1193 | public Object hget(String key) {
1194 | return redisTemplate.opsForHash().entries(key);
1195 | }
1196 |
1197 | /**
1198 | * 删除key下所有值
1199 | *
1200 | * @param key 查询的key
1201 | */
1202 | public void deleteKey(String key) {
1203 | redisTemplate.opsForHash().getOperations().delete(key);
1204 | }
1205 |
1206 | /**
1207 | * 添加set集合
1208 | * @param key
1209 | * @param set
1210 | * @param expire
1211 | */
1212 | public void sset(Object key,Set> set,long expire){
1213 | redisTemplate.opsForSet().add(key,set);
1214 | setKeyExpire(key,expire);
1215 | }
1216 |
1217 | /**
1218 | * 添加set集合
1219 | * @param key
1220 | * @param set
1221 | */
1222 | public void sset(Object key,Set> set){
1223 | sset(key, set,expirationMilliSeconds);
1224 | }
1225 |
1226 | /**
1227 | * 判断key和field下是否有值
1228 | * @param key 判断的key
1229 | * @param field 判断的field
1230 | */
1231 | public Boolean hasKey(String key,String field) {
1232 | return redisTemplate.opsForHash().hasKey(key,field);
1233 | }
1234 |
1235 | /**
1236 | * 判断key下是否有值
1237 | * @param key 判断的key
1238 | */
1239 | public Boolean hasKey(String key) {
1240 | return redisTemplate.opsForHash().getOperations().hasKey(key);
1241 | }
1242 |
1243 | /**
1244 | * 更新key的过期时间
1245 | * @param key
1246 | * @param expire
1247 | */
1248 | public void setKeyExpire(Object key,long expire){
1249 | redisTemplate.expire(key,expire,TimeUnit.MILLISECONDS);
1250 | }
1251 | }
1252 | ```
1253 | ### JWT生成解析工具
1254 | ```java
1255 | import com.lzw.security.entity.User;
1256 | import io.jsonwebtoken.Claims;
1257 | import io.jsonwebtoken.Jwts;
1258 | import io.jsonwebtoken.SignatureAlgorithm;
1259 | import java.util.Date;
1260 | import java.util.Map;
1261 | /**
1262 | * @author: jamesluozhiwei
1263 | * @description: jwt生成token
1264 | */
1265 | public class JwtTokenUtil {
1266 |
1267 | private static final String SALT = "123456";//加密解密盐值
1268 |
1269 | /**
1270 | * 生成token(请根据自身业务扩展)
1271 | * @param subject (主体信息)
1272 | * @param expirationSeconds 过期时间(秒)
1273 | * @param claims 自定义身份信息
1274 | * @return
1275 | */
1276 | public static String generateToken(String subject, int expirationSeconds, Map claims) {
1277 | return Jwts.builder()
1278 | .setClaims(claims)
1279 | .setSubject(subject)//主题
1280 | //.setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000))
1281 | .signWith(SignatureAlgorithm.HS512, SALT) // 不使用公钥私钥
1282 | //.signWith(SignatureAlgorithm.RS256, privateKey)
1283 | .compact();
1284 | }
1285 |
1286 | /**
1287 | * 生成token
1288 | * @param user
1289 | * @return
1290 | */
1291 | public static String generateToken(User user){
1292 | return Jwts.builder()
1293 | .setSubject(user.getId().toString())
1294 | .setExpiration(new Date(System.currentTimeMillis()))
1295 | .setIssuedAt(new Date())
1296 | .setIssuer("JAMES")
1297 | .signWith(SignatureAlgorithm.HS512, SALT)// 不使用公钥私钥
1298 | .compact();
1299 | }
1300 |
1301 | /**
1302 | * 解析token,获得subject中的信息
1303 | * @param token
1304 | * @return
1305 | */
1306 | public static String parseToken(String token) {
1307 | String subject = null;
1308 | try {
1309 | subject = getTokenBody(token).getSubject();
1310 | } catch (Exception e) {
1311 | }
1312 | return subject;
1313 | }
1314 |
1315 | /**
1316 | * 获取token自定义属性
1317 | * @param token
1318 | * @return
1319 | */
1320 | public static Map getClaims(String token){
1321 | Map claims = null;
1322 | try {
1323 | claims = getTokenBody(token);
1324 | }catch (Exception e) {
1325 | }
1326 |
1327 | return claims;
1328 | }
1329 |
1330 | /**
1331 | * 解析token
1332 | * @param token
1333 | * @return
1334 | */
1335 | private static Claims getTokenBody(String token){
1336 | return Jwts.parser()
1337 | //.setSigningKey(publicKey)
1338 | .setSigningKey(SALT)
1339 | .parseClaimsJws(token)
1340 | .getBody();
1341 | }
1342 | }
1343 | ```
1344 | ### 用户实体
1345 | 注意用户实体需要实现 security 的 UserDetails
1346 | ```java
1347 | import org.springframework.security.core.GrantedAuthority;
1348 | import org.springframework.security.core.userdetails.UserDetails;
1349 |
1350 | import java.io.Serializable;
1351 | import java.util.Collection;
1352 | import java.util.Set;
1353 |
1354 | public class User implements UserDetails, Serializable {
1355 |
1356 | private Long id;
1357 |
1358 | private String username;
1359 |
1360 | private String password;
1361 |
1362 | private Set extends GrantedAuthority> authorities;//权限列表
1363 |
1364 | @Override
1365 | public Collection extends GrantedAuthority> getAuthorities() {
1366 | return this.authorities;
1367 | }
1368 |
1369 | @Override
1370 | public String getPassword() {
1371 | return this.password;
1372 | }
1373 |
1374 | @Override
1375 | public String getUsername() {
1376 | return this.username;
1377 | }
1378 |
1379 | @Override
1380 | public boolean isAccountNonExpired() {
1381 | return true;
1382 | }
1383 |
1384 | @Override
1385 | public boolean isAccountNonLocked() {
1386 | return true;
1387 | }
1388 |
1389 | @Override
1390 | public boolean isCredentialsNonExpired() {
1391 | return true;
1392 | }
1393 |
1394 | @Override
1395 | public boolean isEnabled() {
1396 | return true;
1397 | }
1398 |
1399 | public User setUsername(String username) {
1400 | this.username = username;
1401 | return this;
1402 | }
1403 |
1404 | public User setPassword(String password) {
1405 | this.password = password;
1406 | return this;
1407 | }
1408 |
1409 | public User setAuthorities(Set extends GrantedAuthority> authorities) {
1410 | this.authorities = authorities;
1411 | return this;
1412 | }
1413 |
1414 | public Long getId() {
1415 | return id;
1416 | }
1417 |
1418 | public User setId(Long id) {
1419 | this.id = id;
1420 | return this;
1421 | }
1422 | }
1423 | ```
1424 | ### 响应相关
1425 | ```java
1426 | public class GenericResponse {
1427 |
1428 | private boolean success;
1429 | private int statusCode;
1430 | private Object content;
1431 | private String msg;
1432 |
1433 | public boolean isSuccess() {
1434 | return success;
1435 | }
1436 |
1437 | public void setSuccess(boolean success) {
1438 | this.success = success;
1439 | }
1440 |
1441 | public int getStatusCode() {
1442 | return statusCode;
1443 | }
1444 |
1445 | public void setStatusCode(int statusCode) {
1446 | this.statusCode = statusCode;
1447 | }
1448 |
1449 | public Object getContent() {
1450 | return content;
1451 | }
1452 |
1453 | public void setContent(Object content) {
1454 | this.content = content;
1455 | }
1456 |
1457 | public String getMsg() {
1458 | return msg;
1459 | }
1460 |
1461 | public void setMsg(String msg) {
1462 | this.msg = msg;
1463 | }
1464 |
1465 | public GenericResponse(){}
1466 |
1467 | public GenericResponse(boolean success, int code, String msg, Object data) {
1468 |
1469 | this.success = success;
1470 | this.statusCode = code;
1471 | this.msg = msg;
1472 | this.content = data;
1473 | }
1474 |
1475 | public static GenericResponse response(ServiceError error) {
1476 |
1477 | return GenericResponse.response(error, null);
1478 | }
1479 |
1480 | public static GenericResponse response(ServiceError error, Object data) {
1481 |
1482 | if (error == null) {
1483 | error = ServiceError.UN_KNOW_ERROR;
1484 | }
1485 | if (error.equals(ServiceError.NORMAL)) {
1486 | return GenericResponse.response(true, error.getCode(), error.getMsg(), data);
1487 | }
1488 | return GenericResponse.response(false, error.getCode(), error.getMsg(), data);
1489 | }
1490 |
1491 | public static GenericResponse response(boolean success, int code, String msg, Object data) {
1492 |
1493 | return new GenericResponse(success, code, msg, data);
1494 | }
1495 | }
1496 | ```
1497 | ```java
1498 | public enum ServiceError {
1499 |
1500 | NORMAL(1, "操作成功"),
1501 | UN_KNOW_ERROR(-1, "未知错误"),
1502 |
1503 | /** Global Error */
1504 | GLOBAL_ERR_NO_SIGN_IN(-10001,"未登录或登录过期/Not sign in"),
1505 | GLOBAL_ERR_NO_CODE(-10002,"code错误/error code"),
1506 | GLOBAL_ERR_NO_AUTHORITY(-10003, "没有操作权限/No operating rights"),
1507 | ;
1508 |
1509 | private int code;
1510 | private String msg;
1511 |
1512 | private ServiceError(int code, String msg)
1513 | {
1514 | this.code=code;
1515 | this.msg=msg;
1516 | }
1517 |
1518 | public int getCode() {
1519 | return code;
1520 | }
1521 |
1522 | public String getMsg() {
1523 | return msg;
1524 | }
1525 | }
1526 | ```
1527 | ## springboot 启动类
1528 | jar启动请忽略,war启动请继承 SpringBootServletInitializer
1529 | ```java
1530 | import org.springframework.boot.SpringApplication;
1531 | import org.springframework.boot.autoconfigure.SpringBootApplication;
1532 | import org.springframework.boot.builder.SpringApplicationBuilder;
1533 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
1534 |
1535 | @SpringBootApplication
1536 | public class SecurityApplication extends SpringBootServletInitializer {
1537 |
1538 | public static void main(String[] args) {
1539 | SpringApplication.run(SecurityApplication.class, args);
1540 | }
1541 |
1542 | // war启动请实现该方法
1543 | @Override
1544 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
1545 | return builder.sources(SecurityApplication.class);
1546 | }
1547 | }
1548 | ```
1549 | # postman演示
1550 | 未登录访问接口
1551 | 
1552 |
1553 | 登录后携带token访问
1554 | 
1555 | # 项目地址
1556 | github地址:[https://github.com/jamesluozhiwei/security](https://github.com/jamesluozhiwei/security)
1557 |
1558 | 如果对您有帮助请高抬贵手点个star
1559 |
1560 | ---
1561 | 个人博客:[https://ccccyc.cn](https://ccccyc.cn)
1562 |
1563 | 关注公众号获取更多咨询
1564 |
1565 | 
1566 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Mingw, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | ##########################################################################################
204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
205 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
206 | ##########################################################################################
207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
208 | if [ "$MVNW_VERBOSE" = true ]; then
209 | echo "Found .mvn/wrapper/maven-wrapper.jar"
210 | fi
211 | else
212 | if [ "$MVNW_VERBOSE" = true ]; then
213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
214 | fi
215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
216 | while IFS="=" read key value; do
217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
218 | esac
219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
220 | if [ "$MVNW_VERBOSE" = true ]; then
221 | echo "Downloading from: $jarUrl"
222 | fi
223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
224 |
225 | if command -v wget > /dev/null; then
226 | if [ "$MVNW_VERBOSE" = true ]; then
227 | echo "Found wget ... using wget"
228 | fi
229 | wget "$jarUrl" -O "$wrapperJarPath"
230 | elif command -v curl > /dev/null; then
231 | if [ "$MVNW_VERBOSE" = true ]; then
232 | echo "Found curl ... using curl"
233 | fi
234 | curl -o "$wrapperJarPath" "$jarUrl"
235 | else
236 | if [ "$MVNW_VERBOSE" = true ]; then
237 | echo "Falling back to using Java to download"
238 | fi
239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
240 | if [ -e "$javaClass" ]; then
241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
242 | if [ "$MVNW_VERBOSE" = true ]; then
243 | echo " - Compiling MavenWrapperDownloader.java ..."
244 | fi
245 | # Compiling the Java class
246 | ("$JAVA_HOME/bin/javac" "$javaClass")
247 | fi
248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
249 | # Running the downloader
250 | if [ "$MVNW_VERBOSE" = true ]; then
251 | echo " - Running MavenWrapperDownloader.java ..."
252 | fi
253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
254 | fi
255 | fi
256 | fi
257 | fi
258 | ##########################################################################################
259 | # End of extension
260 | ##########################################################################################
261 |
262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
263 | if [ "$MVNW_VERBOSE" = true ]; then
264 | echo $MAVEN_PROJECTBASEDIR
265 | fi
266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
267 |
268 | # For Cygwin, switch paths to Windows format before running java
269 | if $cygwin; then
270 | [ -n "$M2_HOME" ] &&
271 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
272 | [ -n "$JAVA_HOME" ] &&
273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
274 | [ -n "$CLASSPATH" ] &&
275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
276 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
278 | fi
279 |
280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
281 |
282 | exec "$JAVACMD" \
283 | $MAVEN_OPTS \
284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
287 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM https://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM set title of command window
39 | title %0
40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42 |
43 | @REM set %HOME% to equivalent of $HOME
44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45 |
46 | @REM Execute a user defined script before this one
47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
51 | :skipRcPre
52 |
53 | @setlocal
54 |
55 | set ERROR_CODE=0
56 |
57 | @REM To isolate internal variables from possible post scripts, we use another setlocal
58 | @setlocal
59 |
60 | @REM ==== START VALIDATION ====
61 | if not "%JAVA_HOME%" == "" goto OkJHome
62 |
63 | echo.
64 | echo Error: JAVA_HOME not found in your environment. >&2
65 | echo Please set the JAVA_HOME variable in your environment to match the >&2
66 | echo location of your Java installation. >&2
67 | echo.
68 | goto error
69 |
70 | :OkJHome
71 | if exist "%JAVA_HOME%\bin\java.exe" goto init
72 |
73 | echo.
74 | echo Error: JAVA_HOME is set to an invalid directory. >&2
75 | echo JAVA_HOME = "%JAVA_HOME%" >&2
76 | echo Please set the JAVA_HOME variable in your environment to match the >&2
77 | echo location of your Java installation. >&2
78 | echo.
79 | goto error
80 |
81 | @REM ==== END VALIDATION ====
82 |
83 | :init
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122 |
123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | echo Found %WRAPPER_JAR%
132 | ) else (
133 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
134 | echo Downloading from: %DOWNLOAD_URL%
135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
136 | echo Finished downloading %WRAPPER_JAR%
137 | )
138 | @REM End of extension
139 |
140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
141 | if ERRORLEVEL 1 goto error
142 | goto end
143 |
144 | :error
145 | set ERROR_CODE=1
146 |
147 | :end
148 | @endlocal & set ERROR_CODE=%ERROR_CODE%
149 |
150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
154 | :skipRcPost
155 |
156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
158 |
159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
160 |
161 | exit /B %ERROR_CODE%
162 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.1.6.RELEASE
9 |
10 |
11 | com.lzw
12 | security
13 | 0.0.1-SNAPSHOT
14 | security
15 | war
16 | Security project for Spring Boot
17 |
18 |
19 | 1.8
20 | 1.1.8
21 | 1.2.36
22 | 0.9.0
23 |
24 |
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-data-redis
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-security
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-web
37 |
38 |
39 | org.mybatis.spring.boot
40 | mybatis-spring-boot-starter
41 | 2.0.1
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-devtools
47 | runtime
48 | true
49 |
50 |
51 | mysql
52 | mysql-connector-java
53 | runtime
54 |
55 |
56 | org.projectlombok
57 | lombok
58 | true
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-starter-test
63 | test
64 |
65 |
66 | org.springframework.security
67 | spring-security-test
68 | test
69 |
70 |
71 |
72 | io.jsonwebtoken
73 | jjwt
74 | ${jjwt.version}
75 |
76 |
77 |
78 | com.alibaba
79 | fastjson
80 | ${fastjson.version}
81 |
82 |
83 |
84 | com.alibaba
85 | druid
86 | 1.1.8
87 |
88 |
89 |
90 | log4j
91 | log4j
92 | 1.2.17
93 |
94 |
95 |
96 |
97 | org.apache.httpcomponents
98 | httpcore
99 | 4.4.11
100 |
101 |
102 |
103 | org.apache.httpcomponents
104 | httpclient
105 | 4.5.7
106 |
107 |
108 |
109 |
110 | org.bouncycastle
111 | bcprov-jdk15
112 | 1.46
113 |
114 |
115 |
116 | org.codehaus.xfire
117 | xfire-all
118 | 1.2.6
119 |
120 |
121 | org.springframework
122 | spring
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | org.springframework.boot
132 | spring-boot-maven-plugin
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/SecurityApplication.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.builder.SpringApplicationBuilder;
6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
7 |
8 | @SpringBootApplication
9 | public class SecurityApplication extends SpringBootServletInitializer {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(SecurityApplication.class, args);
13 | }
14 |
15 | @Override
16 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
17 | return builder.sources(SecurityApplication.class);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/common/GenericResponse.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.common;
2 |
3 | public class GenericResponse {
4 |
5 | private boolean success;
6 | private int statusCode;
7 | private Object content;
8 | private String msg;
9 |
10 | public boolean isSuccess() {
11 | return success;
12 | }
13 |
14 | public void setSuccess(boolean success) {
15 | this.success = success;
16 | }
17 |
18 | public int getStatusCode() {
19 | return statusCode;
20 | }
21 |
22 | public void setStatusCode(int statusCode) {
23 | this.statusCode = statusCode;
24 | }
25 |
26 | public Object getContent() {
27 | return content;
28 | }
29 |
30 | public void setContent(Object content) {
31 | this.content = content;
32 | }
33 |
34 | public String getMsg() {
35 | return msg;
36 | }
37 |
38 | public void setMsg(String msg) {
39 | this.msg = msg;
40 | }
41 |
42 | public GenericResponse(){}
43 |
44 | public GenericResponse(boolean success, int code, String msg, Object data) {
45 |
46 | this.success = success;
47 | this.statusCode = code;
48 | this.msg = msg;
49 | this.content = data;
50 | }
51 |
52 | public static GenericResponse response(ServiceError error) {
53 |
54 | return GenericResponse.response(error, null);
55 | }
56 |
57 | public static GenericResponse response(ServiceError error, Object data) {
58 |
59 | if (error == null) {
60 | error = ServiceError.UN_KNOW_ERROR;
61 | }
62 | if (error.equals(ServiceError.NORMAL)) {
63 | return GenericResponse.response(true, error.getCode(), error.getMsg(), data);
64 | }
65 | return GenericResponse.response(false, error.getCode(), error.getMsg(), data);
66 | }
67 |
68 | public static GenericResponse response(boolean success, int code, String msg, Object data) {
69 |
70 | return new GenericResponse(success, code, msg, data);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/common/NoPasswordEncoder.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.common;
2 |
3 | import org.springframework.security.crypto.password.PasswordEncoder;
4 |
5 | public class NoPasswordEncoder implements PasswordEncoder {
6 | @Override
7 | public String encode(CharSequence charSequence) {
8 | return "";
9 | }
10 |
11 | @Override
12 | public boolean matches(CharSequence charSequence, String s) {
13 | return true;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/common/ServiceError.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.common;
2 |
3 | public enum ServiceError {
4 |
5 | NORMAL(1, "操作成功"),
6 | UN_KNOW_ERROR(-1, "未知错误"),
7 |
8 | /** Global Error */
9 | GLOBAL_ERR_NO_SIGN_IN(-10001,"未登录或登录过期/Not sign in"),
10 | GLOBAL_ERR_NO_CODE(-10002,"code错误/error code"),
11 | GLOBAL_ERR_NO_AUTHORITY(-10003, "没有操作权限/No operating rights"),
12 |
13 |
14 | ;
15 |
16 |
17 | private int code;
18 | private String msg;
19 |
20 | private ServiceError(int code, String msg)
21 | {
22 | this.code=code;
23 | this.msg=msg;
24 | }
25 |
26 | public int getCode() {
27 | return code;
28 | }
29 |
30 | public String getMsg() {
31 | return msg;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/common/WeChatUrl.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.common;
2 |
3 | public enum WeChatUrl {
4 |
5 | JS_CODE_2_SESSION("https://api.weixin.qq.com/sns/jscode2session")
6 | ,GET_ACCESS_TOKEN("https://api.weixin.qq.com/cgi-bin/token")
7 | ,SEND_TEMPLATE_MESSAGE("https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send")
8 | ;
9 |
10 | private String url;
11 |
12 | WeChatUrl() {
13 | }
14 |
15 | WeChatUrl(String url) {
16 | this.url = url;
17 | }
18 |
19 | public String getUrl() {
20 | return url;
21 | }
22 |
23 | public WeChatUrl setUrl(String url) {
24 | this.url = url;
25 | return this;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/config/DruidConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.config;
2 |
3 | import com.alibaba.druid.pool.DruidDataSource;
4 | import com.alibaba.druid.support.http.StatViewServlet;
5 | import com.alibaba.druid.support.http.WebStatFilter;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
8 | import org.springframework.boot.web.servlet.ServletRegistrationBean;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 | import org.springframework.context.annotation.PropertySource;
12 |
13 | import javax.sql.DataSource;
14 |
15 | /**
16 | * druid参数配置
17 | * @author jamesluozhiwei
18 | */
19 | @Configuration
20 | @PropertySource(value = "classpath:application.yml")
21 | public class DruidConfiguration {
22 |
23 | /**
24 | * @author jamesluozhiwei
25 | * @todo 数据源配置
26 | */
27 | @Bean(destroyMethod = "close", initMethod = "init")
28 | @ConfigurationProperties(prefix = "spring.datasource")
29 | public DataSource druidDataSource() {
30 | DruidDataSource druidDataSource = new DruidDataSource();
31 | return druidDataSource;
32 | }
33 |
34 | /**
35 | * druid
36 | * 注册一个StatViewServlet
37 | * @return
38 | */
39 | @Bean
40 | public ServletRegistrationBean druidStatViewServlet(){
41 | //org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
42 | ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
43 |
44 | //添加初始化参数:initParams
45 | //白名单:
46 | //servletRegistrationBean.addInitParameter("allow","127.0.0.1");//如果不配置或为空则表示允许所有地址访问
47 | //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
48 | //servletRegistrationBean.addInitParameter("deny","192.168.1.73");
49 | //登录查看信息的账号密码.
50 | servletRegistrationBean.addInitParameter("loginUsername","admin");
51 | servletRegistrationBean.addInitParameter("loginPassword","123456");
52 | //是否能够重置数据.
53 | servletRegistrationBean.addInitParameter("resetEnable","false");
54 | return servletRegistrationBean;
55 | }
56 |
57 | /**
58 | * druid过滤器
59 | * 注册一个:filterRegistrationBean
60 | * @return
61 | */
62 | @Bean
63 | public FilterRegistrationBean druidStatFilter(){
64 | FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
65 | //添加过滤规则.
66 | filterRegistrationBean.addUrlPatterns("/*");
67 | //添加不需要忽略的格式信息.
68 | filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
69 | return filterRegistrationBean;
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/config/SpringSecurityConf.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.config;
2 |
3 | import com.lzw.security.common.NoPasswordEncoder;
4 | import com.lzw.security.filter.JwtAuthenticationTokenFilter;
5 | import com.lzw.security.handler.*;
6 | import com.lzw.security.service.SelfUserDetailsService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.http.HttpMethod;
11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
12 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
15 | import org.springframework.security.config.core.GrantedAuthorityDefaults;
16 | import org.springframework.security.config.http.SessionCreationPolicy;
17 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
18 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
19 |
20 | /**
21 | * @author: jamesluozhiwei
22 | * @description:
23 | */
24 | @Configuration
25 | @EnableGlobalMethodSecurity(prePostEnabled = true)
26 | public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
27 |
28 |
29 | @Autowired
30 | AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)
31 |
32 | @Autowired
33 | AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)
34 |
35 | @Autowired
36 | AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)
37 |
38 | @Autowired
39 | AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
40 |
41 | @Autowired
42 | AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
43 |
44 | @Autowired
45 | SelfUserDetailsService userDetailsService; // 自定义user
46 |
47 | @Autowired
48 | JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
49 |
50 | @Override
51 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
52 | // 加入自定义的安全认证
53 | //auth.authenticationProvider(provider);
54 | auth.userDetailsService(userDetailsService).passwordEncoder(new NoPasswordEncoder());//这里使用自定义的加密方式(不使用加密),security提供了 BCryptPasswordEncoder 加密可自定义或使用这个
55 | }
56 |
57 | @Override
58 | protected void configure(HttpSecurity http) throws Exception {
59 |
60 | // 去掉 CSRF
61 | http.csrf().disable()
62 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //关闭session管理,使用token机制处理
63 | .and()
64 |
65 | .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
66 | //.and().antMatcher("/login")
67 | //.and().authorizeRequests().anyRequest().access("@rbacauthorityservice.hasPermission(request,authentication)")// 自定义权限校验 RBAC 动态 url 认证
68 | .and().authorizeRequests().antMatchers(HttpMethod.GET,"/test").hasAuthority("test:list")
69 | .and().authorizeRequests().antMatchers(HttpMethod.POST,"/test").hasAuthority("test:add")
70 | .and().authorizeRequests().antMatchers(HttpMethod.PUT,"/test").hasAuthority("test:update")
71 | .and().authorizeRequests().antMatchers(HttpMethod.DELETE,"/test").hasAuthority("test:delete")
72 | .and().authorizeRequests().antMatchers("/test/*").hasAuthority("test:manager")
73 | .and().authorizeRequests().antMatchers("/login").permitAll() //放行login(这里使用自定义登录)
74 | .and().authorizeRequests().antMatchers("/hello").permitAll();
75 |
76 | // .and()
77 | // .formLogin() //开启登录, 定义当需要用户登录时候,转到的登录页面
78 | // .loginPage("/test/login.html")
79 | // .loginProcessingUrl("/login")
80 | // .successHandler(authenticationSuccessHandler) // 登录成功
81 | // .failureHandler(authenticationFailureHandler) // 登录失败
82 | // .permitAll()
83 |
84 | // .and()
85 | // .logout()//默认注销行为为logout
86 | // .logoutUrl("/logout")
87 | // .logoutSuccessHandler(logoutSuccessHandler)
88 | // .permitAll();
89 |
90 | // 记住我
91 | // http.rememberMe().rememberMeParameter("remember-me")
92 | // .userDetailsService(userDetailsService).tokenValiditySeconds(1000);
93 |
94 | http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
95 | http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
96 |
97 | }
98 |
99 | @Bean
100 | GrantedAuthorityDefaults grantedAuthorityDefaults(){
101 | return new GrantedAuthorityDefaults("");//remove the ROLE_ prefix
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/controller/TestController.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.controller;
2 |
3 | import com.lzw.security.common.GenericResponse;
4 | import com.lzw.security.common.ServiceError;
5 | import com.lzw.security.service.WeChatService;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.security.access.prepost.PreAuthorize;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.PostMapping;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | @RestController
13 | public class TestController {
14 |
15 | @Autowired
16 | private WeChatService weChatService;
17 |
18 | /**
19 | * code登录获取用户openid
20 | * @param code
21 | * @return
22 | * @throws Exception
23 | */
24 | @PostMapping("/login")
25 | public GenericResponse login(String code)throws Exception{
26 | return weChatService.wxLogin(code);
27 | }
28 |
29 | /**
30 | * 权限测试
31 | */
32 |
33 | @GetMapping("/test")
34 | public GenericResponse test(){
35 | return GenericResponse.response(ServiceError.NORMAL,"test");
36 | }
37 |
38 | @PostMapping("/test")
39 | public GenericResponse testPost(){
40 | return GenericResponse.response(ServiceError.NORMAL,"testPOST");
41 | }
42 |
43 | @GetMapping("/test/a")
44 | public GenericResponse testA(){
45 | return GenericResponse.response(ServiceError.NORMAL,"testManage");
46 | }
47 |
48 | @GetMapping("/hello")
49 | public GenericResponse hello(){
50 | return GenericResponse.response(ServiceError.NORMAL,"hello security");
51 | }
52 |
53 | @GetMapping("/ddd")
54 | @PreAuthorize("hasAuthority('ddd:list')")
55 | public GenericResponse ddd(){
56 | return GenericResponse.response(ServiceError.NORMAL,"dddList");
57 | }
58 |
59 | @PostMapping("/ddd")
60 | @PreAuthorize("hasAuthority('ddd:add')")
61 | public GenericResponse dddd(){
62 | return GenericResponse.response(ServiceError.NORMAL,"testPOST");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/entity/User.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.entity;
2 |
3 | import org.springframework.security.core.GrantedAuthority;
4 | import org.springframework.security.core.userdetails.UserDetails;
5 |
6 | import java.io.Serializable;
7 | import java.util.Collection;
8 | import java.util.Set;
9 |
10 | public class User implements UserDetails, Serializable {
11 |
12 | private Long id;
13 |
14 | private String username;
15 |
16 | private String password;
17 |
18 | private Set extends GrantedAuthority> authorities;//权限列表
19 |
20 | @Override
21 | public Collection extends GrantedAuthority> getAuthorities() {
22 | return this.authorities;
23 | }
24 |
25 | @Override
26 | public String getPassword() {
27 | return this.password;
28 | }
29 |
30 | @Override
31 | public String getUsername() {
32 | return this.username;
33 | }
34 |
35 | @Override
36 | public boolean isAccountNonExpired() {
37 | return true;
38 | }
39 |
40 | @Override
41 | public boolean isAccountNonLocked() {
42 | return true;
43 | }
44 |
45 | @Override
46 | public boolean isCredentialsNonExpired() {
47 | return true;
48 | }
49 |
50 | @Override
51 | public boolean isEnabled() {
52 | return true;
53 | }
54 |
55 | public User setUsername(String username) {
56 | this.username = username;
57 | return this;
58 | }
59 |
60 | public User setPassword(String password) {
61 | this.password = password;
62 | return this;
63 | }
64 |
65 | public User setAuthorities(Set extends GrantedAuthority> authorities) {
66 | this.authorities = authorities;
67 | return this;
68 | }
69 |
70 | public Long getId() {
71 | return id;
72 | }
73 |
74 | public User setId(Long id) {
75 | this.id = id;
76 | return this;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/filter/JwtAuthenticationTokenFilter.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.filter;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import com.lzw.security.entity.User;
7 | import com.lzw.security.service.SelfUserDetailsService;
8 | import com.lzw.security.util.*;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.beans.factory.annotation.Value;
12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13 | import org.springframework.security.core.GrantedAuthority;
14 | import org.springframework.security.core.context.SecurityContextHolder;
15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
16 | import org.springframework.stereotype.Component;
17 | import org.springframework.web.filter.OncePerRequestFilter;
18 |
19 | import javax.servlet.FilterChain;
20 | import javax.servlet.ServletException;
21 | import javax.servlet.http.HttpServletRequest;
22 | import javax.servlet.http.HttpServletResponse;
23 | import java.io.IOException;
24 | import java.util.HashMap;
25 | import java.util.Set;
26 |
27 | /**
28 | * @author: jamesluozhiwei
29 | * @description: 确保在一次请求只通过一次filter,而不需要重复执行
30 | */
31 | @Component
32 | @Slf4j
33 | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
34 |
35 | @Value("${token.expirationMilliSeconds}")
36 | private long expirationMilliSeconds;
37 |
38 | @Autowired
39 | SelfUserDetailsService userDetailsService;
40 |
41 | @Autowired
42 | RedisUtil redisUtil;
43 |
44 | @Override
45 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
46 | String authHeader = request.getHeader("Authorization");
47 | response.setCharacterEncoding("utf-8");
48 | if (null == authHeader || !authHeader.startsWith("Bearer ")){
49 | filterChain.doFilter(request,response);//token格式不正确
50 | return;
51 | }
52 | String authToken = authHeader.substring("Bearer ".length());
53 |
54 | String subject = JwtTokenUtil.parseToken(authToken);//获取在token中自定义的subject,用作用户标识,用来获取用户权限
55 |
56 | //获取redis中的token信息
57 |
58 | if (!redisUtil.hasKey(authToken)){
59 | //token 不存在 返回错误信息
60 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
61 | return;
62 | }
63 |
64 | //获取缓存中的信息(根据自己的业务进行拓展)
65 | HashMap hashMap = (HashMap) redisUtil.hget(authToken);
66 | //从tokenInfo中取出用户信息
67 | User user = new User();
68 | user.setId(Long.parseLong(hashMap.get("id").toString())).setAuthorities((Set extends GrantedAuthority>) hashMap.get("authorities"));
69 | if (null == hashMap){
70 | //用户信息不存在或转换错误,返回错误信息
71 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
72 | return;
73 | }
74 | //更新token过期时间
75 | redisUtil.setKeyExpire(authToken,expirationMilliSeconds);
76 | //将信息交给security
77 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
78 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
79 | SecurityContextHolder.getContext().setAuthentication(authenticationToken);
80 | filterChain.doFilter(request,response);
81 | }
82 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/handler/AjaxAccessDeniedHandler.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import org.springframework.security.access.AccessDeniedException;
7 | import org.springframework.security.web.access.AccessDeniedHandler;
8 | import org.springframework.stereotype.Component;
9 |
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 |
15 | /**
16 | * @author: jamesluozhiwei
17 | * @description: 无权访问
18 | */
19 | @Component
20 | public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
21 | @Override
22 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
23 | response.setCharacterEncoding("utf-8");
24 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_AUTHORITY)));
25 | }
26 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/handler/AjaxAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import org.springframework.security.core.AuthenticationException;
7 | import org.springframework.security.web.AuthenticationEntryPoint;
8 | import org.springframework.stereotype.Component;
9 |
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 |
15 | /**
16 | * @author: jamesluozhiwei
17 | * @description: 用户未登录时返回给前端的数据
18 | */
19 | @Component
20 | public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
21 |
22 | @Override
23 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
24 | request.setCharacterEncoding("utf-8");
25 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN)));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/handler/AjaxAuthenticationFailureHandler.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.handler;
2 |
3 |
4 | import com.alibaba.fastjson.JSON;
5 | import com.lzw.security.common.GenericResponse;
6 | import com.lzw.security.common.ServiceError;
7 | import org.springframework.security.core.AuthenticationException;
8 | import org.springframework.security.web.authentication.AuthenticationFailureHandler;
9 | import org.springframework.stereotype.Component;
10 |
11 | import javax.servlet.ServletException;
12 | import javax.servlet.http.HttpServletRequest;
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.IOException;
15 |
16 | /**
17 | * @author: jamesluozhiwei
18 | * @description: 用户登录失败时返回给前端的数据
19 | */
20 | @Component
21 | public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
22 |
23 | @Override
24 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
25 | response.setCharacterEncoding("utf-8");
26 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_CODE)));
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/handler/AjaxAuthenticationSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import com.lzw.security.util.RedisUtil;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.security.core.Authentication;
10 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
11 | import org.springframework.stereotype.Component;
12 |
13 | import javax.servlet.ServletException;
14 | import javax.servlet.http.HttpServletRequest;
15 | import javax.servlet.http.HttpServletResponse;
16 | import java.io.IOException;
17 |
18 | /**
19 | * @author: jamesluozhiwei
20 | * @description: 用户登录成功时返回给前端的数据
21 | */
22 | @Component
23 | @Slf4j
24 | public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
25 |
26 | @Autowired
27 | private RedisUtil redisUtil;
28 |
29 | @Override
30 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
31 | //自定义login,不走这里、若使用security的formLogin则自己添加业务实现(生成token、存储token等等)
32 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.NORMAL)));
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/handler/AjaxLogoutSuccessHandler.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import com.lzw.security.util.RedisUtil;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.security.core.Authentication;
10 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
11 | import org.springframework.stereotype.Component;
12 |
13 | import javax.servlet.ServletException;
14 | import javax.servlet.http.HttpServletRequest;
15 | import javax.servlet.http.HttpServletResponse;
16 | import java.io.IOException;
17 |
18 | /**
19 | * @author: jamesluozhiwei
20 | * @description: 登出成功
21 | */
22 | @Component
23 | @Slf4j
24 | public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
25 |
26 | @Autowired
27 | private RedisUtil redisUtil;
28 |
29 | @Override
30 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
31 | //没有logout不走这里、若使用security的formLogin则自己添加业务实现(移除token等等)
32 | response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.NORMAL)));
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/service/RbacAuthorityService.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.service;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.util.AntPathMatcher;
8 |
9 | import javax.servlet.http.HttpServletRequest;
10 | import java.util.Collection;
11 | import java.util.HashSet;
12 | import java.util.Iterator;
13 | import java.util.Set;
14 |
15 | /**
16 | * 鉴权处理
17 | */
18 | @Component("rbacauthorityservice")
19 | public class RbacAuthorityService {
20 | /**
21 | * 自定义鉴权
22 | * @param request
23 | * @param authentication 用户权限信息
24 | * @return 通过返回true 不通过则返回false(所有鉴权只要有一个通过了则为通过)
25 | */
26 | public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
27 | Object userInfo = authentication.getPrincipal();
28 |
29 | boolean hasPermission = false;
30 |
31 | if (userInfo instanceof UserDetails) {
32 |
33 | String username = ((UserDetails) userInfo).getUsername();
34 |
35 | //获取资源
36 | Set urls = new HashSet();
37 | // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
38 | // 模拟鉴权(可根据自己的业务扩展)
39 | urls.add("/demo/**");//application.yml里设置了项目路径,百度一下我就不贴了
40 | Set set2 = new HashSet();
41 | Set set3 = new HashSet();
42 |
43 | AntPathMatcher antPathMatcher = new AntPathMatcher();
44 |
45 | for (String url : urls) {
46 | if (antPathMatcher.match(url, request.getRequestURI())) {
47 | hasPermission = true;
48 | break;
49 | }
50 | }
51 | return hasPermission;
52 | } else {
53 | return false;
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/service/SelfUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.service;
2 | import com.lzw.security.entity.User;
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Component;
9 |
10 | import java.util.HashSet;
11 | import java.util.Set;
12 |
13 | /**
14 | * 用户认证、权限、使用security的表单登录时会被调用(自定义登录请忽略)
15 | * @author: jamesluozhiwei
16 | */
17 | @Component
18 | @Slf4j
19 | public class SelfUserDetailsService implements UserDetailsService {
20 |
21 | //@Autowired
22 | //private UserMapper userMapper;
23 |
24 | /**
25 | * 若使用security表单鉴权则需实现该方法,通过username获取用户信息(密码、权限等等)
26 | * @param username
27 | * @return
28 | * @throws UsernameNotFoundException
29 | */
30 | @Override
31 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
32 |
33 | //通过username查询用户
34 | //根据自己的业务获取用户信息
35 | //SelfUserDetails user = userMapper.getUser(username);
36 | //模拟从数据库获取到用户信息
37 | User user = new User();
38 | if(user == null){
39 | //仍需要细化处理
40 | throw new UsernameNotFoundException("该用户不存在");
41 | }
42 |
43 | Set authoritiesSet = new HashSet();
44 | // 模拟从数据库中获取用户权限
45 | authoritiesSet.add(new SimpleGrantedAuthority("test:list"));
46 | authoritiesSet.add(new SimpleGrantedAuthority("test:add"));
47 | user.setAuthorities(authoritiesSet);
48 |
49 | log.info("用户{}验证通过",username);
50 | return user;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/service/WeChatService.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.service;
2 |
3 | import com.lzw.security.common.GenericResponse;
4 |
5 | /**
6 | * 微信业务接口
7 | */
8 | public interface WeChatService {
9 |
10 | /**
11 | * 小程序登录
12 | * @param code
13 | * @return
14 | */
15 | GenericResponse wxLogin(String code)throws Exception;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/service/impl/WeChatServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.service.impl;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.lzw.security.common.GenericResponse;
5 | import com.lzw.security.common.ServiceError;
6 | import com.lzw.security.entity.User;
7 | import com.lzw.security.service.WeChatService;
8 | import com.lzw.security.util.Jcode2SessionUtil;
9 | import com.lzw.security.util.JwtTokenUtil;
10 | import com.lzw.security.util.RedisUtil;
11 | import lombok.extern.slf4j.Slf4j;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.beans.factory.annotation.Value;
14 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
15 | import org.springframework.stereotype.Service;
16 | import org.springframework.util.Assert;
17 |
18 | import java.util.HashMap;
19 | import java.util.HashSet;
20 | import java.util.Set;
21 |
22 | /**
23 | * 微信业务实现类
24 | */
25 | @Service
26 | @Slf4j
27 | public class WeChatServiceImpl implements WeChatService {
28 |
29 | @Value("${weChat.appid}")
30 | private String appid;
31 |
32 | @Value("${weChat.secret}")
33 | private String secret;
34 |
35 | @Autowired
36 | private RedisUtil redisUtil;
37 |
38 | @Override
39 | public GenericResponse wxLogin(String code) throws Exception{
40 | JSONObject sessionInfo = JSONObject.parseObject(jcode2Session(code));
41 |
42 | Assert.notNull(sessionInfo,"code 无效");
43 |
44 | Assert.isTrue(0 == sessionInfo.getInteger("errcode"),sessionInfo.getString("errmsg"));
45 |
46 | // 获取用户唯一标识符 openid成功
47 | // 模拟从数据库获取用户信息
48 | User user = new User();
49 | user.setId(1L);
50 | Set authoritiesSet = new HashSet();
51 | // 模拟从数据库中获取用户权限
52 | authoritiesSet.add(new SimpleGrantedAuthority("test:add"));
53 | authoritiesSet.add(new SimpleGrantedAuthority("test:list"));
54 | authoritiesSet.add(new SimpleGrantedAuthority("ddd:list"));
55 | user.setAuthorities(authoritiesSet);
56 | HashMap hashMap = new HashMap<>();
57 | hashMap.put("id",user.getId().toString());
58 | hashMap.put("authorities",authoritiesSet);
59 | String token = JwtTokenUtil.generateToken(user);
60 | redisUtil.hset(token,hashMap);
61 |
62 | return GenericResponse.response(ServiceError.NORMAL,token);
63 | }
64 |
65 | /**
66 | * 登录凭证校验
67 | * @param code
68 | * @return
69 | * @throws Exception
70 | */
71 | private String jcode2Session(String code)throws Exception{
72 | String sessionInfo = Jcode2SessionUtil.jscode2session(appid,secret,code,"authorization_code");//登录grantType固定
73 | log.info(sessionInfo);
74 | return sessionInfo;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/AccessAddressUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 |
7 | /**
8 | * @author: jamesluozhiwei
9 | * @description: 自定义访问地址工具类
10 | * 获取请求的ip地址等信息
11 | */
12 | @Component
13 | public class AccessAddressUtil {
14 |
15 | /**
16 | * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
17 | * 参考文章: http://developer.51cto.com/art/201111/305181.htm
18 | *
19 | * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
20 | * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
21 | *
22 | * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
23 | * 192.168.1.100
24 | *
25 | * 用户真实IP为: 192.168.1.110
26 | * @param request
27 | * @return
28 | */
29 | public static String getIpAddress(HttpServletRequest request) {
30 | String ip = request.getHeader("x-forwarded-for");
31 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
32 | ip = request.getHeader("Proxy-Client-IP");
33 | }
34 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
35 | ip = request.getHeader("WL-Proxy-Client-IP");
36 | }
37 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
38 | ip = request.getHeader("HTTP_CLIENT_IP");
39 | }
40 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
41 | ip = request.getHeader("HTTP_X_FORWARDED_FOR");
42 | }
43 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
44 | ip = request.getRemoteAddr();
45 | }
46 | return ip;
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/CollectionUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 |
4 | import java.util.Iterator;
5 | import java.util.Map;
6 |
7 | /**
8 | * @author: jamesluoizhiwei
9 | * @description: 集合工具类
10 | *
11 | */
12 | public class CollectionUtil {
13 |
14 | /**
15 | * @param map 取值的集合
16 | * @param key 所想取值的集合的key
17 | * @return 返回key对应的value
18 | */
19 | public static String getMapValue(Map map,String key){
20 | String result = null;
21 | if(map != null){
22 | Iterator iterable = map.keySet().iterator();
23 | while (iterable.hasNext()){
24 | Object object = iterable.next();
25 | if(key.equals(object))
26 | if(map.get(object) != null)
27 | result = map.get(object).toString();
28 | }
29 | }
30 |
31 | return result;
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/DateUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 |
4 |
5 | import org.springframework.util.StringUtils;
6 |
7 | import java.text.DateFormat;
8 | import java.text.ParseException;
9 | import java.text.SimpleDateFormat;
10 | import java.util.ArrayList;
11 | import java.util.Calendar;
12 | import java.util.Date;
13 | import java.util.List;
14 |
15 | /**
16 | * @author jamesluozhiwei
17 | */
18 | public class DateUtil {
19 |
20 | /**
21 | * 日期格式 yyyy-MM-dd
22 | */
23 | public final static String DATEFORMAT = "yyyy-MM-dd";
24 | /**
25 | * 日期格式 yyyyMMdd
26 | */
27 | public final static String DATEFORMAT2 = "yyyyMMdd";
28 | /**
29 | * 日期格式 yyyy/MM/dd
30 | */
31 | public final static String DATEFORMAT3 = "yyyy/MM/dd";
32 | /**
33 | * 时间格式 HH:mm:ss
34 | */
35 | public final static String TIMEFORMAT = "HH:mm:ss";
36 | /**
37 | * 日期时间格式 yyyy-MM-dd HH:mm:ss
38 | */
39 | public final static String DATETIMEFORMAT = "yyyy-MM-dd HH:mm:ss";
40 |
41 | /**
42 | * yyyy-MM
43 | */
44 | public final static String DATE_SHORT_FORMAT = "yyyy-MM";
45 | /**
46 | * yyyyMM
47 | */
48 | public final static String DATE_SHORT_FORMAT2 = "yyyyMM";
49 |
50 | public final static SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
51 |
52 | public static String DATETIME_FORMAT_REGEX = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}";//yyyy-MM-dd HH:mm:ss
53 | public static String DATE_FORMAT_REGEX = "\\d{4}-\\d{2}-\\d{2}";//yyyy-MM-dd
54 | public static String SHORTDATE_FORMAT_REGEX = "\\d{4}-\\d{2}";//yyyy-MM
55 | public static String DATE_FORMAT2_REGEX = "\\d{4}\\d{2}\\d{2}";//yyyyMMdd
56 | public static String SHORTDATE_FORMAT2_REGEX = "\\d{4}\\d{2}";//yyyyMM
57 |
58 | /**
59 | * 获取时间日期格式化dateformat
60 | *
61 | * @param pattern 格式
62 | * @return
63 | */
64 | public static DateFormat getFormat(String pattern) {
65 | if (StringUtils.isEmpty(pattern)) {
66 | return null;
67 | }
68 | return new SimpleDateFormat(pattern);
69 | }
70 |
71 | /**
72 | * 将string类型date格式化为date类型
73 | *
74 | * @param dateString string类型date
75 | * @param pattern 格式
76 | * @return
77 | */
78 | public static Date getDateFormat(String dateString, String pattern) {
79 | if (StringUtils.isEmpty(dateString)) {
80 | return null;
81 | }
82 | try {
83 | return getFormat(pattern).parse(dateString);
84 | } catch (ParseException e) {
85 | e.printStackTrace();
86 | return null;
87 | }
88 | }
89 |
90 | public static String getStringFormat(Date date, String pattern) {
91 | if (null == date) {
92 | return null;
93 | }
94 | return getFormat(pattern).format(date);
95 | }
96 |
97 | /**
98 | * 将string类型date重新格式化为string类型
99 | *
100 | * @param dateString
101 | * @param srcPattern
102 | * @param targetPattern
103 | * @return
104 | */
105 | public static String getStringReformat(String dateString, String srcPattern, String targetPattern) {
106 | Date date = getDateFormat(dateString, srcPattern);
107 | return getStringFormat(date, targetPattern);
108 | }
109 |
110 | /**
111 | * 获取当前时间的前一天
112 | *
113 | * @return
114 | */
115 | public static Date getYesterday() {
116 | Calendar calendar = Calendar.getInstance();
117 | calendar.add(Calendar.DATE, -1);
118 | calendar.set(Calendar.HOUR_OF_DAY, 0);
119 | calendar.set(Calendar.MINUTE, 0);
120 | calendar.set(Calendar.SECOND, 0);
121 | return calendar.getTime();
122 | }
123 |
124 | /**
125 | * 获取本月的第一天
126 | *
127 | * @return
128 | */
129 | public static Date getFirstDayByMonth() {
130 | Calendar calendar = Calendar.getInstance();
131 | calendar.set(Calendar.DAY_OF_MONTH, 1);
132 | calendar.set(Calendar.HOUR_OF_DAY, 0);
133 | calendar.set(Calendar.MINUTE, 0);
134 | calendar.set(Calendar.SECOND, 0);
135 | return calendar.getTime();
136 | }
137 |
138 | /**
139 | * 获取上一个月的第一天
140 | *
141 | * @return
142 | */
143 | public static Date getFirstDayByLastMonth() {
144 | Calendar calendar = Calendar.getInstance();
145 | calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) - 1);
146 | calendar.set(Calendar.DAY_OF_MONTH, 1);
147 | calendar.set(Calendar.HOUR_OF_DAY, 0);
148 | calendar.set(Calendar.MINUTE, 0);
149 | calendar.set(Calendar.SECOND, 0);
150 | return calendar.getTime();
151 | }
152 |
153 | /**
154 | * 根据月份获取当月第一天
155 | *
156 | * @param month
157 | * @return
158 | */
159 | public static Date getFirstDayByMonth(int month) {
160 | Calendar calendar = Calendar.getInstance();
161 | calendar.set(Calendar.MONTH, month);
162 | calendar.set(Calendar.DAY_OF_MONTH, 1);
163 | calendar.set(Calendar.HOUR_OF_DAY, 0);
164 | calendar.set(Calendar.MINUTE, 0);
165 | calendar.set(Calendar.SECOND, 0);
166 | return calendar.getTime();
167 | }
168 |
169 | /**
170 | * 获取某个月份的第一天
171 | *
172 | * @param month
173 | * @return
174 | */
175 | public static Date getFirstDayByMonth(Date date, int month) {
176 | Calendar calendar = Calendar.getInstance();
177 | calendar.setTime(date);
178 | calendar.set(Calendar.MONTH, month);
179 | calendar.set(Calendar.DAY_OF_MONTH, 1);
180 | calendar.set(Calendar.HOUR_OF_DAY, 0);
181 | calendar.set(Calendar.MINUTE, 0);
182 | calendar.set(Calendar.SECOND, 0);
183 | return calendar.getTime();
184 | }
185 |
186 | /**
187 | * 计算两个日期之间相隔的月份
188 | *
189 | * @param startDate
190 | * @param endDate
191 | * @return
192 | */
193 | public static List intervalMonths(Date startDate, Date endDate) {
194 | List monthList = new ArrayList();
195 |
196 | Calendar tmp = Calendar.getInstance();
197 | tmp.setTime(startDate);
198 | tmp.set(Calendar.DAY_OF_MONTH, tmp.getActualMaximum(Calendar.DAY_OF_MONTH) - 1);
199 |
200 | Calendar start = Calendar.getInstance();
201 | start.setTime(startDate);
202 | start.set(Calendar.DAY_OF_MONTH, 1);
203 |
204 | Calendar end = Calendar.getInstance();
205 | end.setTime(endDate);
206 | end.set(Calendar.DAY_OF_MONTH, end.getActualMaximum(Calendar.DAY_OF_MONTH));
207 |
208 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_SHORT_FORMAT);
209 | for (; tmp.after(start) && tmp.before(end); tmp.add(Calendar.MONTH, 1), tmp.set(Calendar.DAY_OF_MONTH, tmp.getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) {
210 | monthList.add(sdf.format(tmp.getTime()));
211 | }
212 | return monthList;
213 | }
214 |
215 | /**
216 | * 传入时间和当前时间比较最大获取上个月,返回虽然Date,实际时间是yyyyMM01 00:00:00.0
217 | *
218 | * @param date
219 | * @return
220 | */
221 | public static Date maxLastMonth(Date date) {
222 | Date thisMonth = DateUtil.getDateFormat(DateUtil.getStringFormat(new Date(), DateUtil.DATE_SHORT_FORMAT), DateUtil.DATE_SHORT_FORMAT);
223 | if (thisMonth.compareTo(date) <= 0) {
224 | Calendar calendar = Calendar.getInstance();
225 | calendar.setTime(thisMonth);
226 | calendar.add(Calendar.MONTH, -1);
227 | return calendar.getTime();
228 | }
229 | return date;
230 | }
231 |
232 | public static Date getMonth(Date date, int month) {
233 | Calendar calendar = Calendar.getInstance();
234 | calendar.setTime(date);
235 | calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) + month);
236 | return calendar.getTime();
237 | }
238 |
239 | public static Date getFirstDayByMonth(Date date) {
240 | Calendar calendar = Calendar.getInstance();
241 | calendar.setTime(date);
242 | calendar.set(Calendar.DAY_OF_MONTH, 1);
243 | calendar.set(Calendar.HOUR_OF_DAY, 0);
244 | calendar.set(Calendar.MINUTE, 0);
245 | calendar.set(Calendar.SECOND, 0);
246 | return calendar.getTime();
247 | }
248 |
249 | public static int betweenDays(Date beforeDate, Date afterDate) {
250 | if (null == beforeDate || null == afterDate) {
251 | throw new NullPointerException("date can't be null");
252 | }
253 | if (beforeDate.after(afterDate)) {
254 | return 0;
255 | }
256 | return (int) ((afterDate.getTime() - beforeDate.getTime()) / (1000 * 3600 * 24));
257 | }
258 |
259 | /**
260 | * 获取当前时间的后i天
261 | * 精确到秒
262 | * @param i
263 | * @return
264 | */
265 | public static String getAddDayTime(int i){
266 | Date date = new Date(System.currentTimeMillis()+i*24*60*60*1000);
267 | return sdfTime.format(date);
268 | }
269 |
270 | /**
271 | * 获取当前时间的+多少秒
272 | * 精确到秒
273 | * @param i
274 | * @return
275 | */
276 | public static String getAddDaySecond(int i){
277 | Date date = new Date(System.currentTimeMillis()+i*1000);
278 | return sdfTime.format(date);
279 | }
280 |
281 | /**
282 | * 获取当前时间的YYYY-MM-DD HH:mm:ss格式
283 | *
284 | * @return
285 | */
286 | public static String getTime() {
287 | return sdfTime.format(new Date());
288 | }
289 |
290 | /**
291 | * 日期比较,如果s>=e 返回true 否则返回false
292 | * @param s
293 | * @param e
294 | * @return
295 | */
296 | public static boolean compareDate(String s, String e) {
297 | if(formatDate(s)==null||formatDate(e)==null){
298 | return false;
299 | }
300 | // return fomatDate(s).getTime() >=fomatDate(e).getTime();
301 | return s.compareTo(e)>0;
302 | }
303 |
304 | /**
305 | * 格式化日期
306 | * @param date
307 | * @return
308 | */
309 | public static Date formatDate(String date) {
310 | DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
311 | try {
312 | return fmt.parse(date);
313 | } catch (ParseException e) {
314 | e.printStackTrace();
315 | return null;
316 | }
317 | }
318 | }
319 |
320 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/HttpUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import org.apache.http.HttpEntity;
5 | import org.apache.http.HttpResponse;
6 | import org.apache.http.client.methods.HttpGet;
7 | import org.apache.http.client.methods.HttpPost;
8 | import org.apache.http.entity.StringEntity;
9 | import org.apache.http.impl.client.DefaultHttpClient;
10 | import org.apache.http.util.EntityUtils;
11 |
12 | import java.io.*;
13 | import java.net.HttpURLConnection;
14 | import java.net.URL;
15 | import java.util.Base64;
16 |
17 | /**
18 | * 请求工具类
19 | * @author jamesluozhiwei
20 | */
21 | public class HttpUtil {
22 |
23 | /**
24 | * 发送get请求
25 | * @param url
26 | * @return
27 | */
28 | public static String sendGet(String url){
29 | DefaultHttpClient httpClient = new DefaultHttpClient();
30 | HttpGet httpGet = new HttpGet(url);
31 | String result = null;
32 | try {
33 | HttpResponse response = httpClient.execute(httpGet);
34 | HttpEntity entity = response.getEntity();
35 | if (entity != null) {
36 | result = EntityUtils.toString(entity, "UTF-8");
37 | }
38 | httpGet.releaseConnection();
39 | } catch (IOException e) {
40 | e.printStackTrace();
41 | }
42 | return result;
43 | }
44 |
45 |
46 | /**
47 | * 发送post请求
48 | * @param url
49 | * @param params 可使用JSONObject转JSON字符串
50 | * @return
51 | */
52 | public static String sendPost(String url,String params){
53 | DefaultHttpClient httpClient = new DefaultHttpClient();
54 | HttpPost httpPost = new HttpPost(url);
55 | JSONObject jsonObject = null;
56 | try {
57 | httpPost.setEntity(new StringEntity(params, "UTF-8"));
58 | HttpResponse response = httpClient.execute(httpPost);
59 | return EntityUtils.toString(response.getEntity(),"UTF-8");
60 | } catch (IOException e) {
61 | e.printStackTrace();
62 | }
63 | return null;
64 | }
65 |
66 | /**
67 | * 发送post请求
68 | * @param httpUrl
69 | * @param param JSON字符串
70 | * @return
71 | */
72 | public static String doPostBase64(String httpUrl, String param) {
73 |
74 | HttpURLConnection connection = null;
75 | InputStream is = null;
76 | OutputStream os = null;
77 | BufferedReader br = null;
78 | String result = null;
79 | try {
80 | URL url = new URL(httpUrl);
81 | // 通过远程url连接对象打开连接
82 | connection = (HttpURLConnection) url.openConnection();
83 | // 设置连接请求方式
84 | connection.setRequestMethod("POST");
85 | // 设置连接主机服务器超时时间:15000毫秒
86 | connection.setConnectTimeout(15000);
87 | // 设置读取主机服务器返回数据超时时间:60000毫秒
88 | connection.setReadTimeout(60000);
89 |
90 | // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
91 | connection.setDoOutput(true);
92 | // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
93 | connection.setDoInput(true);
94 | // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。
95 | connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
96 | // 通过连接对象获取一个输出流
97 | os = connection.getOutputStream();
98 | // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
99 |
100 | os.write(param.getBytes());
101 |
102 | // 通过连接对象获取一个输入流,向远程读取
103 | if (connection.getResponseCode() == 200) {
104 |
105 | is = connection.getInputStream();
106 |
107 | ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
108 | byte[] buff = new byte[100];
109 | int rc = 0;
110 | while ((rc = is.read(buff, 0, 100)) > 0) {
111 | swapStream.write(buff, 0, rc);
112 | }
113 | byte[] in2b = swapStream.toByteArray();
114 | String tmp = new String(in2b);
115 | if (tmp.indexOf("errcode") == -1)
116 | return Base64.getEncoder().encodeToString(in2b);
117 | return tmp;
118 | }
119 | } catch (Exception e) {
120 | e.printStackTrace();
121 | } finally {
122 | // 关闭资源
123 | if (null != br) {
124 | try {
125 | br.close();
126 | } catch (IOException e) {
127 | e.printStackTrace();
128 | }
129 | }
130 | if (null != os) {
131 | try {
132 | os.close();
133 | } catch (IOException e) {
134 | e.printStackTrace();
135 | }
136 | }
137 | if (null != is) {
138 | try {
139 | is.close();
140 | } catch (IOException e) {
141 | e.printStackTrace();
142 | }
143 | }
144 | // 断开与远程地址url的连接
145 | connection.disconnect();
146 | }
147 | return result;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/Jcode2SessionUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.lzw.security.common.WeChatUrl;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.apache.http.NameValuePair;
7 | import org.apache.http.client.entity.UrlEncodedFormEntity;
8 | import org.apache.http.client.methods.HttpPost;
9 | import org.apache.http.impl.client.DefaultHttpClient;
10 | import org.apache.http.message.BasicNameValuePair;
11 | import org.apache.http.util.EntityUtils;
12 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
13 | import org.codehaus.xfire.util.Base64;
14 |
15 | import javax.crypto.Cipher;
16 | import javax.crypto.spec.IvParameterSpec;
17 | import javax.crypto.spec.SecretKeySpec;
18 | import java.security.AlgorithmParameters;
19 | import java.security.Security;
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.List;
23 |
24 | @Slf4j
25 | public class Jcode2SessionUtil {
26 |
27 | /**
28 | * 请求微信后台获取用户数据
29 | * @param code wx.login获取到的临时code
30 | * @return 请求结果
31 | * @throws Exception
32 | */
33 | public static String jscode2session(String appid,String secret,String code,String grantType)throws Exception{
34 | //定义返回的json对象
35 | JSONObject result = new JSONObject();
36 | //创建请求通过code换取session等数据
37 | HttpPost httpPost = new HttpPost(WeChatUrl.JS_CODE_2_SESSION.getUrl());
38 | List params=new ArrayList();
39 | //建立一个NameValuePair数组,用于存储欲传送的参数
40 | params.add(new BasicNameValuePair("appid",appid));
41 | params.add(new BasicNameValuePair("secret",secret));
42 | params.add(new BasicNameValuePair("js_code",code));
43 | params.add(new BasicNameValuePair("grant_type",grantType));
44 | //设置编码
45 | httpPost.setEntity(new UrlEncodedFormEntity(params));//添加参数
46 | return EntityUtils.toString(new DefaultHttpClient().execute(httpPost).getEntity());
47 | }
48 | /**
49 | * 解密用户敏感数据获取用户信息
50 | * @param sessionKey 数据进行加密签名的密钥
51 | * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
52 | * @param iv 加密算法的初始向量
53 | * @return
54 | */
55 | public static String getUserInfo(String encryptedData,String sessionKey,String iv)throws Exception{
56 | // 被加密的数据
57 | byte[] dataByte = Base64.decode(encryptedData);
58 | // 加密秘钥
59 | byte[] keyByte = Base64.decode(sessionKey);
60 | // 偏移量
61 | byte[] ivByte = Base64.decode(iv);
62 | // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
63 | int base = 16;
64 | if (keyByte.length % base != 0) {
65 | int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
66 | byte[] temp = new byte[groups * base];
67 | Arrays.fill(temp, (byte) 0);
68 | System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
69 | keyByte = temp;
70 | }
71 | // 初始化
72 | Security.addProvider(new BouncyCastleProvider());
73 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
74 | SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
75 | AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
76 | parameters.init(new IvParameterSpec(ivByte));
77 | cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
78 | byte[] resultByte = cipher.doFinal(dataByte);
79 | if (null != resultByte && resultByte.length > 0) {
80 | String result = new String(resultByte, "UTF-8");
81 | log.info(result);
82 | return result;
83 | }
84 | return null;
85 | }
86 |
87 | /**
88 | * 获取微信接口调用凭证
89 | * @param appid
90 | * @param secret
91 | * @return 返回String 可转JSON
92 | */
93 | public static String getAccessToken(String appid,String secret){
94 | JSONObject params = new JSONObject();
95 | params.put("grant_type","client_credential");//获取接口调用凭证
96 | params.put("appid",appid);
97 | params.put("secret",secret);
98 | return HttpUtil.sendGet(WeChatUrl.GET_ACCESS_TOKEN.getUrl()+"?grant_type=client_credential&appid=" + appid + "&secret=" + secret);
99 | }
100 |
101 | /**
102 | * 发送模板消息
103 | * @param access_token 接口调用凭证
104 | * @param touser 接收者(用户)的 openid
105 | * @param template_id 所需下发的模板消息id
106 | * @param page 点击模版卡片后跳转的页面,仅限本小程序内的页面。支持带参数,(eg:index?foo=bar)。该字段不填则模版无法跳转
107 | * @param form_id 表单提交场景下,为submit事件带上的formId;支付场景下,为本次支付的 prepay_id
108 | * @param data 模版内容,不填则下发空模版。具体格式请参照官网示例
109 | * @param emphasis_keyword 模版需要放大的关键词,不填则默认无放大
110 | * @return 返回String可转JSON
111 | */
112 | public static String sendTemplateMessage(String access_token,String touser,String template_id,String page,String form_id,Object data,String emphasis_keyword){
113 | JSONObject params = new JSONObject();
114 | params.put("touser",touser);
115 | params.put("template_id",template_id);
116 | if (null != page && !"".equals(page)){
117 | params.put("page",page);
118 | }
119 | params.put("form_id",form_id);
120 | params.put("data",data);
121 | if (null != emphasis_keyword && !"".equals(emphasis_keyword)){
122 | params.put("emphasis_keyword",emphasis_keyword);
123 | }
124 | //发送请求
125 | return HttpUtil.sendPost(WeChatUrl.SEND_TEMPLATE_MESSAGE.getUrl() + "?access_token=" + access_token,params.toString());
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/JwtTokenUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 |
4 | import com.lzw.security.entity.User;
5 | import io.jsonwebtoken.Claims;
6 | import io.jsonwebtoken.Jwts;
7 | import io.jsonwebtoken.SignatureAlgorithm;
8 |
9 | import java.util.Date;
10 | import java.util.Map;
11 |
12 | /**
13 | * @author: jamesluozhiwei
14 | * @description: jwt生成token
15 | */
16 | public class JwtTokenUtil {
17 |
18 | private static final String SALT = "123456";//加密解密盐值
19 |
20 | /**
21 | * 生成token(请根据自身业务扩展)
22 | * @param subject (主体信息)
23 | * @param expirationSeconds 过期时间(秒)
24 | * @param claims 自定义身份信息
25 | * @return
26 | */
27 | public static String generateToken(String subject, int expirationSeconds, Map claims) {
28 | return Jwts.builder()
29 | .setClaims(claims)
30 | .setSubject(subject)//主题
31 | //.setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000))
32 | .signWith(SignatureAlgorithm.HS512, SALT) // 不使用公钥私钥
33 | //.signWith(SignatureAlgorithm.RS256, privateKey)
34 | .compact();
35 | }
36 |
37 | /**
38 | * 生成token
39 | * @param user
40 | * @return
41 | */
42 | public static String generateToken(User user){
43 | return Jwts.builder()
44 | .setSubject(user.getId().toString())
45 | .setExpiration(new Date(System.currentTimeMillis()))
46 | .setIssuedAt(new Date())
47 | .setIssuer("JAMES")
48 | .signWith(SignatureAlgorithm.HS512, SALT)// 不使用公钥私钥
49 | .compact();
50 | }
51 |
52 | /**
53 | * 解析token,获得subject中的信息
54 | * @param token
55 | * @return
56 | */
57 | public static String parseToken(String token) {
58 | String subject = null;
59 | try {
60 | subject = getTokenBody(token).getSubject();
61 | } catch (Exception e) {
62 | }
63 | return subject;
64 | }
65 |
66 | /**
67 | * 获取token自定义属性
68 | * @param token
69 | * @return
70 | */
71 | public static Map getClaims(String token){
72 | Map claims = null;
73 | try {
74 | claims = getTokenBody(token);
75 | }catch (Exception e) {
76 | }
77 |
78 | return claims;
79 | }
80 |
81 |
82 | /**
83 | * 解析token
84 | * @param token
85 | * @return
86 | */
87 | private static Claims getTokenBody(String token){
88 | return Jwts.parser()
89 | //.setSigningKey(publicKey)
90 | .setSigningKey(SALT)
91 | .parseClaimsJws(token)
92 | .getBody();
93 | }
94 | }
--------------------------------------------------------------------------------
/src/main/java/com/lzw/security/util/RedisUtil.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security.util;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.data.redis.core.RedisTemplate;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.HashMap;
9 | import java.util.Set;
10 | import java.util.concurrent.TimeUnit;
11 |
12 | /**
13 | * redis工具类
14 | * @author: jamesluozhiwei
15 | */
16 | @Component
17 | public class RedisUtil {
18 |
19 | @Value("${token.expirationMilliSeconds}")
20 | private long expirationMilliSeconds;
21 |
22 | //@Autowired
23 | //private StringRedisTemplate redisTemplate;
24 |
25 | @Autowired
26 | private RedisTemplate redisTemplate;
27 |
28 | /**
29 | * 查询key,支持模糊查询
30 | * @param key
31 | * */
32 | public Set keys(String key){
33 | return redisTemplate.keys(key);
34 | }
35 |
36 | /**
37 | * 字符串获取值
38 | * @param key
39 | * */
40 | public Object get(String key){
41 | return redisTemplate.opsForValue().get(key);
42 | }
43 |
44 | /**
45 | * 字符串存入值
46 | * 默认过期时间为2小时
47 | * @param key
48 | * */
49 | public void set(String key, String value){
50 | set(key,value,expirationMilliSeconds);
51 | }
52 |
53 | /**
54 | * 字符串存入值
55 | * @param expire 过期时间(毫秒计)
56 | * @param key
57 | * */
58 | public void set(String key, String value,long expire){
59 | redisTemplate.opsForValue().set(key,value, expire,TimeUnit.MILLISECONDS);
60 | }
61 |
62 | /**
63 | * 删出key
64 | * 这里跟下边deleteKey()最底层实现都是一样的,应该可以通用
65 | * @param key
66 | * */
67 | public void delete(String key){
68 | redisTemplate.opsForValue().getOperations().delete(key);
69 | }
70 |
71 | /**
72 | * 添加单个
73 | * @param key key
74 | * @param filed filed
75 | * @param domain 对象
76 | */
77 | public void hset(String key,String filed,Object domain){
78 | hset(key,filed,domain,expirationMilliSeconds);
79 | }
80 |
81 | /**
82 | * 添加单个
83 | * @param key key
84 | * @param filed filed
85 | * @param domain 对象
86 | * @param expire 过期时间(毫秒计)
87 | */
88 | public void hset(String key,String filed,Object domain,long expire){
89 | redisTemplate.opsForHash().put(key, filed, domain);
90 | setKeyExpire(key,expirationMilliSeconds);
91 | }
92 |
93 | /**
94 | * 添加HashMap
95 | *
96 | * @param key key
97 | * @param hm 要存入的hash表
98 | */
99 | public void hset(String key, HashMap hm){
100 | redisTemplate.opsForHash().putAll(key,hm);
101 | setKeyExpire(key,expirationMilliSeconds);
102 | }
103 |
104 | /**
105 | * 如果key存在就不覆盖
106 | * @param key
107 | * @param filed
108 | * @param domain
109 | */
110 | public void hsetAbsent(String key,String filed,Object domain){
111 | redisTemplate.opsForHash().putIfAbsent(key, filed, domain);
112 | }
113 |
114 | /**
115 | * 查询key和field所确定的值
116 | * @param key 查询的key
117 | * @param field 查询的field
118 | * @return HV
119 | */
120 | public Object hget(String key,String field) {
121 | return redisTemplate.opsForHash().get(key, field);
122 | }
123 |
124 | /**
125 | * 查询该key下所有值
126 | * @param key 查询的key
127 | * @return Map
128 | */
129 | public Object hget(String key) {
130 | return redisTemplate.opsForHash().entries(key);
131 | }
132 |
133 | /**
134 | * 删除key下所有值
135 | *
136 | * @param key 查询的key
137 | */
138 | public void deleteKey(String key) {
139 | redisTemplate.opsForHash().getOperations().delete(key);
140 | }
141 |
142 | /**
143 | * 添加set集合
144 | * @param key
145 | * @param set
146 | * @param expire
147 | */
148 | public void sset(Object key,Set> set,long expire){
149 | redisTemplate.opsForSet().add(key,set);
150 | setKeyExpire(key,expire);
151 | }
152 |
153 | /**
154 | * 添加set集合
155 | * @param key
156 | * @param set
157 | */
158 | public void sset(Object key,Set> set){
159 | sset(key, set,expirationMilliSeconds);
160 | }
161 |
162 | /**
163 | * 判断key和field下是否有值
164 | * @param key 判断的key
165 | * @param field 判断的field
166 | */
167 | public Boolean hasKey(String key,String field) {
168 | return redisTemplate.opsForHash().hasKey(key,field);
169 | }
170 |
171 | /**
172 | * 判断key下是否有值
173 | * @param key 判断的key
174 | */
175 | public Boolean hasKey(String key) {
176 | return redisTemplate.opsForHash().getOperations().hasKey(key);
177 | }
178 |
179 | /**
180 | * 更新key的过期时间
181 | * @param key
182 | * @param expire
183 | */
184 | public void setKeyExpire(Object key,long expire){
185 | redisTemplate.expire(key,expire,TimeUnit.MILLISECONDS);
186 | }
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: root
4 | password: 123456
5 | url: jdbc:mysql://localhost:3306/db_XXX?characterEncoding=utf-8&useSSl=false
6 | driver-class-name: com.mysql.jdbc.Driver
7 | type: com.alibaba.druid.pool.DruidDataSource
8 | #监控统计拦截的filters
9 | filters: stat,wall,log4j
10 | #druid配置
11 | #配置初始化大小/最小/最大
12 | initialSize: 5
13 | minIdle: 5
14 | maxActive: 20
15 | #获取连接等待超时时间
16 | maxWait: 60000
17 | #间隔多久进行一次检测,检测需要关闭的空闲连接
18 | timeBetweenEvictionRunsMillis: 60000
19 | #一个连接在池中最小生存的时间
20 | minEvictableIdleTimeMillis: 300000
21 | validationQuery: SELECT 1 FROM DUAL
22 | testWhileIdle: true
23 | testOnBorrow: false
24 | testOnReturn: false
25 | #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
26 | poolPreparedStatements: false
27 | maxPoolPreparedStatementPerConnectionSize: 20
28 | # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
29 | connectionProperties:
30 | druid:
31 | stat:
32 | mergeSql: true
33 | slowSqlMillis: 5000
34 | http:
35 | encoding:
36 | charset: utf-8
37 | force: true
38 | enabled: true
39 | redis:
40 | host: 127.0.0.1
41 | port: 6379
42 | password: 123456
43 |
44 |
45 | #mybatis是独立节点,需要单独配置
46 | mybatis:
47 | mapper-locations: classpath*:mapper/*.xml
48 | type-aliases-package: com.lzw.security.entity
49 | configuration:
50 | map-underscore-to-camel-case: true
51 |
52 | server:
53 | port: 8080
54 | tomcat:
55 | uri-encoding: utf-8
56 | servlet:
57 | context-path: /
58 |
59 | #自定义参数,可以迁移走
60 | token:
61 | #redis默认过期时间(2小时)(这是自定义的)(毫秒)
62 | expirationMilliSeconds: 7200000
63 |
64 | #微信相关参数
65 | weChat:
66 | #小程序appid
67 | appid: aaaaaaaaaaaaaaaa
68 | #小程序密钥
69 | secret: ssssssssssssssss
70 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/src/test/java/com/lzw/security/SecurityApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.lzw.security;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class SecurityApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------