SpringSecurity

快速入门

搭建一个SpringBoot工程

创建一个maven项目
导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

创建启动类SecurityApplication.java

1
2
3
4
5
6
7
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}

编写controller

1
2
3
4
5
6
7
8
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello";
}
}

运行项目

引入SpringSecurity

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

重启项目, 再次请求http://localhost:8080/

可以发现此时需要先登录, 才能进行刚刚的正常访问

账号是: user
密码将会在idea控制台中打印出来 dd7079e7-0fb9-4735-850b-ccc3aaceea32

输入完账号密码之后又可以正常访问了

发送请求http://localhost:8080/logout退出登录

常见的三个过滤器

FilterSecurityInterceptor: 是一个方法级的权限过滤器, 基本位于过滤链的最底部。

ExceptionTranslationFilter: 是个异常过滤器,用来处理在认证授权过程中抛出的异常

UsernamePasswordAuthenticationFilter: 对/login 的 POST 请求做拦截,校验表单中用户名,密码。

设置登录系统的账号密码

通过配置文件方式配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring-security?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
security:
user:
name: xiamu
password: 123456

server:
port: 666

通过config配置类实现

运行这个配置类的前提是一定要保证boot版本是2.2.1.RELEASE

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>

此时登录的账户是: xiamu
密码是: 123456

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123456");
auth.inMemoryAuthentication().withUser("xiamu").password(password).roles("admin");
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}

当出现了这个异常
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

不能正常登录时, 记得将如下Bean创建出来

1
2
3
4
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

自定义编写实现类实现接口

注释掉SecurityConfig类上的@Configuration

1
2
// @Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {...}

然后编写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}

接着创建一个MyUserDetailsService来实现UserDetailsService接口
注意User引入的包是: org.springframework.security.core.userdetails.User

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.security.core.userdetails.User;

@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("xiamu", new BCryptPasswordEncoder().encode("123456"), auths);
}
}

然后运行boot

查询数据库完成用户认证

第一步, 引入依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

第二步, 然后创建数据库

第三步, 配置yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring-security?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# security:
# user:
# name: xiamu
# password: 123456

server:
port: 666

第四步, 创建实体类

1
2
3
4
5
6
7
@Data
public class Users {
private Integer id;
private String username;
private String password;
}

第五步, 创建mapper接口

1
2
3
@Repository
public interface UserMapper extends BaseMapper<User> {
}

第六步, 修改service, 查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用usersMapper方法, 根据用户名查询数据库
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
Users users = userMapper.selectOne(queryWrapper);
// 判断, 数据库没有用户名, 认证失败
if (users == null) {
throw new UsernameNotFoundException("用户名不存在! ");
}

List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
// 从查询数据库返回users对象 得到用户名和密码, 返回
return new User("xiamu",
new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}
}

第七步, 启动类开启包扫描

1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("icu.xiamu.mapper")
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}

测试代码:

自定义设置登录页面

自定义设置登录页面, 不需要认证就可以访问

在SecurityConfig1中重写configure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径 , 也就是表单action需要提交的路径
.defaultSuccessUrl("/index")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/hello", "/user/login").permitAll() // 设置哪些路径可以直接访问, 不需要认证
.anyRequest()
.authenticated()
.and()
.csrf()
.disable(); // 关闭csrf防护
}

resource/static/login.html创建一个登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名: <input type="text" name="username" /> <br>
密码: <input type="text" name="password" /> <br>
<input type="submit" value="login">
</form>
</body>
</html>

在HelloController中多添加一个mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello";
}

@GetMapping("/index")
public String index() {
return "index";
}
}

测试代码:

这个路径可以直接请求, 不需要认证
http://localhost:666/hello

请求 http://localhost:666/index 的时候需要进行认证

认证完成之后就会显示出来index

用户授权

hasAuthority

修改configure方法

添加

1
2
.antMatchers("/index")      // 当前登录用户, 只有具有admin才可以访问这个路径
.hasAuthority("admin")

并且让MyUserDetailsService中的commaSeparatedStringToAuthorityList设置成admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用usersMapper方法, 根据用户名查询数据库
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
Users users = userMapper.selectOne(queryWrapper);
// 判断, 数据库没有用户名, 认证失败
if (users == null) {
throw new UsernameNotFoundException("用户名不存在! ");
}

List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
// 从查询数据库返回users对象 得到用户名和密码, 返回
return new User("xiamu",
new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}
}

此时只有一个权限是admin, 也就是说admin权限可以进行登录

代码测试:
访问http://localhost:666/index

hasAnyAuthority

如果想实现多个权限, 需要在SecurityConfig1中使用hasAnyAuthority

1
2
.antMatchers("/index")
.hasAnyAuthority("admin, manager")

此时登录的权限是admin或者是manager都可以进行登录

完整SecurityConfig1的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/index")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/hello", "/user/login")
.permitAll() // 设置哪些路径可以直接访问, 不需要认证
// .antMatchers("/index") // 当前登录用户, 只有具有admin才可以访问这个路径
// .hasAuthority("admin")
.antMatchers("/index")
.hasAnyAuthority("admin, manager")
.anyRequest()
.authenticated()
.and()
.csrf()
.disable(); // 关闭csrf防护
}
}

hasRole

指定角色, 是当前角色的用户才能进行登录

需要注意的是commaSeparatedStringToAuthorityList中填写的角色应该是要添加前缀的ROLE_

1
2
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("manager,ROLE_sale");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/index")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/hello", "/user/login")
.permitAll() // 设置哪些路径可以直接访问, 不需要认证
// .antMatchers("/index") // 当前登录用户, 只有具有admin才可以访问这个路径
// .hasAuthority("admin")
// .antMatchers("/index")
// .hasAnyAuthority("admin, manager")
.antMatchers("/index")
.hasRole("sale")
.anyRequest()
.authenticated()
.and()
.csrf()
.disable(); // 关闭csrf防护
}

hasAnyRole

这个跟hasAnyAuthority差不多, 可以设置多个角色, 跟hasRole一样, 需要添加前缀ROLE_

自定义403页面

当登录的用户没有访问权限或者是角色时, 尝试登录会出现默认的403页面, 这个页面是可以通过配置类进行替换的

在static中创建一个自定义的403页面 unauth.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>非法用户, 不能访问</h2>
</body>
</html>
1
2
// 配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");

完整SecurityConfig1的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/index")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/hello", "/user/login")
.permitAll() // 设置哪些路径可以直接访问, 不需要认证
// .antMatchers("/index") // 当前登录用户, 只有具有admin才可以访问这个路径
// .hasAuthority("admin")
// .antMatchers("/index")
// .hasAnyAuthority("admin, manager")
.antMatchers("/index")
.hasAnyRole("sale")
.anyRequest()
.authenticated()
.and()
.csrf()
.disable(); // 关闭csrf防护
}
}

此时再次非法请求一下, 显示的是我们自己创建的html页面

注解方式的用户授权

@Secured

用户具有某个角色, 可以方法这个方法

启动类或者配置类开启注解

1
@EnableGlobalMethodSecurity(securedEnabled = true)

在controller的方法上面使用注解, 设置角色

1
2
3
4
5
6
@GetMapping("/update")
@Secured({"ROLE_sale", "ROLE_manager"})
public String update() {
return "update";
}

MyUserDetailsService设置用户角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用usersMapper方法, 根据用户名查询数据库
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
Users users = userMapper.selectOne(queryWrapper);
// 判断, 数据库没有用户名, 认证失败
if (users == null) {
throw new UsernameNotFoundException("用户名不存在! ");
}

List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
// 从查询数据库返回users对象 得到用户名和密码, 返回
return new User("xiamu",
new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}
}

PreAuthorize

启动类开启注解

1
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

在controller的方法上面使用注解

1
2
3
4
5
6
@GetMapping("/update")
// @Secured({"ROLE_sale", "ROLE_manager"})
@PreAuthorize("hasAnyAuthority('admin')")
public String update() {
return "update";
}

同样的MyUserDetailsService设置用户角色

1
2
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");

PreAuthorize可以设置hasAnyAuthority, hasAuthority, hasAnyRole, hasRole

@PostAuthorize

@PostAuthorize注解, 是在方法执行完成之后再进行校验权限, 适合校验带有返回值

启动类开启注解

1
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

controller设置@PostAuthorize注解

1
2
3
4
5
6
7
8
9
@GetMapping("/update")
// @Secured({"ROLE_sale", "ROLE_manager"})
// @PreAuthorize("hasAnyAuthority('admin')")
@PostAuthorize("hasAnyAuthority('admin123')")
public String update() {
System.out.println("update...");
return "update";
}

当请求了 http://localhost:666/update 成功登录之后, 控制将会打印这段, 但是页面将会进行权限验证

@PostFilter

@PostFilter: 权限验证之后对数据进行过滤, 留下用户名是admin1的数据
表达式种的filterObject引用的是方法返回值List中的某一个元素

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<Users> getAllUser(){
ArrayList<Users> list = new ArrayList<>();
list.add(new Users(11,"admin1","6666"));
list.add(new Users(21,"admin2","888"));
return list;
}

页面最后只展示了admin1的信息

@PreFilter

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<Users> getTestPreFilter(@RequestBody List<Users>
list){
list.forEach(t-> {
System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}

使用postman发送post请求 http://localhost:666/user/login?username=xiamu&password=123456

之后将会返回一个cookie

1
2
JSESSIONID
57DD13E39F7BDAD3C871CCB4DA0664EF

然后再给postman发送请求
http://localhost:666/getTestPreFilter

在header中携带cookie

Body中发送JSON数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"id": "1",
"username": "admin1",
"password": "666"
},
{
"id": "2",
"username": "admin2",
"password": "888"
},
{
"id": "3",
"username": "admin3",
"password": "999"
},
{
"id": "4",
"username": "admin4",
"password": "999"
}
]

可以发现最后返回的id都是偶数的, 这说明了传入的数据在后台进行了过滤, 过滤掉了id为奇数的

用户注销

在configure中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/hello").permitAll();

// 配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/success.html")
.permitAll()
...
}

接着浏览器请求 http://localhost:666/success.html 浏览器跳转到=> http://localhost:666/login.html

输入账号密码登录, 登录成功之后跳转到success.html页面

此时是可以直接访问 http://localhost:666/index

然后点击退出, 此时退出之后再次请求, http://localhost:666/index 就是需要输入账号密码的

自动登录(功能实现)

修改login.html, 添加一个input复选框, 记得name需要是remember-me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名: <input type="text" name="username" /> <br>
密码: <input type="text" name="password" /> <br>
<input type="checkbox" name="remember-me" /> 自动登录 <br>
<input type="submit" value="login">
</form>
</body>
</html>

然后创建表

来源于JdbcTokenRepositoryImpl类中, 在idea double shift可以搜到

1
2
3
4
5
6
7
create table persistent_logins 
(
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
)

然后修改SecurityConfig1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 注入数据源
@Autowired
private DataSource dataSource;

// 给jdbcTokenRepository设置数据源
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}

// 配置token的过期时间
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) // 设置有效时长, 单位秒

完整SecurityConfig1代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;

// 注入数据源
@Autowired
private DataSource dataSource;

@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}

@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/hello").permitAll();

// 配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() // 自定义自己编写的登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/success.html")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/hello", "/user/login")
.permitAll() // 设置哪些路径可以直接访问, 不需要认证
// .antMatchers("/index") // 当前登录用户, 只有具有admin才可以访问这个路径
// .hasAuthority("admin")
// .antMatchers("/index")
// .hasAnyAuthority("admin, manager")
.antMatchers("/index")
.hasAnyRole("sale")
.anyRequest()
.authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) // 设置有效时长, 单位秒
.and()
.csrf()
.disable(); // 关闭csrf防护
}
}

代码测试步骤, 请求 http://localhost:666/index , 登录的时候需要勾选记住我


之后关闭浏览器, 再次请求 http://localhost:666/index 这个时候就不需要输入密码就能直接看见index页面

同样的数据库将会保存对应的token到表中, 一旦超过了60秒, 就需要再次输入一次密码

CSRF功能

请求 http://localhost:8080/update_token

开启csrf防护

1
2
3
4
5
6
//关闭csrf保护功能
//http.csrf().disable();

<!-- 然后页面中, 在提交form, 传入一个token -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

项目demo:

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<!--对Thymeleaf添加Spring Security标签支持-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/spring-security?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# druid数据库连接池配置
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 20 # 初始化数据连接池大小
min-idle: 10 # 空闲数
max-active: 100 #最大连接数


#配置Mybatis的相关属性
mybatis:
mapper-locations: classpath:mapper/*.xml #指定mapper XML文件的位置
#type-aliases-package: com.zsc.mybatis_demo.domain #指定实体类的别名的映射路径

configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印输出SQL语句
map-underscore-to-camel-case: true # 启动驼峰式转换
use-generated-keys: true #开启自增组件
#debug: true

SecurityConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
UserDetailsService userDetailsService;

//实现用户身份认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
//配置url的访问权限
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/**update**").permitAll()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated();

//关闭csrf保护功能
//http.csrf().disable();
//
//使用自定义的登录窗口
http.formLogin()
.loginPage("/userLogin").permitAll()
.usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/")
.failureUrl("/userLogin?error");
}
}

CSRFController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class CSRFController {

@GetMapping("/toupdate")
public String test(Model model){
return "csrf/csrfTest";
}

@PostMapping("/update_token")
public String getToken() {
return "csrf/csrf_token";
}
}

LoginController.java

1
2
3
4
5
6
7
8
@Controller
public class LoginController {
@GetMapping("/userLogin")
public String login(){
return "login/login";
}
}

UserDetailsServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("role"));
UserDetails userDetails = new User("lucy", new BCryptPasswordEncoder().encode("123")
, list);
return userDetails;
}
}

CsrfSecurityApplication.java

1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class CsrfSecurityApplication {

public static void main(String[] args) {
SpringApplication.run(CsrfSecurityApplication.class, args);
}

}

csrf_token.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户修改</title>
</head>
<body>
<div>
<span th:text="${_csrf.token}"></span>

</div>

</body>
</html>

csrfTest.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户修改</title>
</head>
<body>
<div align="center">
<form method="post" action="update_token">
<!-- -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
用户名: <input type="text" name="username" /><br />
&nbsp;&nbsp;码: <input type="password" name="password" /><br />
<button type="submit">修改</button>
</form>
</div>
</body>
</html>

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户登录界面</title>
</head>
<body class="text-center">
<form class="form-signin" th:method="post" th:action="@{/userLogin}">
<h1 class="h3 mb-3 font-weight-normal">请登录</h1>

<input type="text" class="form-control" placeholder="用户名" required="" autofocus="" name="username">
<input type="password" class="form-control" placeholder="密码" required="" name="password">
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="rememberme"> 记住我
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button>
</form>
</body>
</html>

SpringSecurity整合到微服务项目

导入sql

创建数据库acldb, 然后导入如下sql到该数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# Host: localhost  (Version 5.7.19)
# Date: 2019-11-18 15:49:15
# Generator: MySQL-Front 6.1 (Build 1.26)


#
# Structure for table "acl_permission"
#

CREATE TABLE `acl_permission` (
`id` char(19) NOT NULL DEFAULT '' COMMENT '编号',
`pid` char(19) NOT NULL DEFAULT '' COMMENT '所属上级',
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '名称',
`type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '类型(1:菜单,2:按钮)',
`permission_value` varchar(50) DEFAULT NULL COMMENT '权限值',
`path` varchar(100) DEFAULT NULL COMMENT '访问路径',
`component` varchar(100) DEFAULT NULL COMMENT '组件路径',
`icon` varchar(50) DEFAULT NULL COMMENT '图标',
`status` tinyint(4) DEFAULT NULL COMMENT '状态(0:禁止,1:正常)',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_pid` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限';

#
# Data for table "acl_permission"
#

INSERT INTO `acl_permission` VALUES ('1','0','全部数据',0,NULL,NULL,NULL,NULL,NULL,0,'2019-11-15 17:13:06','2019-11-15 17:13:06'),('1195268474480156673','1','权限管理',1,NULL,'/acl','Layout',NULL,NULL,0,'2019-11-15 17:13:06','2019-11-18 13:54:25'),('1195268616021139457','1195268474480156673','用户管理',1,NULL,'user/list','/acl/user/list',NULL,NULL,0,'2019-11-15 17:13:40','2019-11-18 13:53:12'),('1195268788138598401','1195268474480156673','角色管理',1,NULL,'role/list','/acl/role/list',NULL,NULL,0,'2019-11-15 17:14:21','2019-11-15 17:14:21'),('1195268893830864898','1195268474480156673','菜单管理',1,NULL,'menu/list','/acl/menu/list',NULL,NULL,0,'2019-11-15 17:14:46','2019-11-15 17:14:46'),('1195269143060602882','1195268616021139457','查看',2,'user.list','','',NULL,NULL,0,'2019-11-15 17:15:45','2019-11-17 21:57:16'),('1195269295926206466','1195268616021139457','添加',2,'user.add','user/add','/acl/user/form',NULL,NULL,0,'2019-11-15 17:16:22','2019-11-15 17:16:22'),('1195269473479483394','1195268616021139457','修改',2,'user.update','user/update/:id','/acl/user/form',NULL,NULL,0,'2019-11-15 17:17:04','2019-11-15 17:17:04'),('1195269547269873666','1195268616021139457','删除',2,'user.remove','','',NULL,NULL,0,'2019-11-15 17:17:22','2019-11-15 17:17:22'),('1195269821262782465','1195268788138598401','修改',2,'role.update','role/update/:id','/acl/role/form',NULL,NULL,0,'2019-11-15 17:18:27','2019-11-15 17:19:53'),('1195269903542444034','1195268788138598401','查看',2,'role.list','','',NULL,NULL,0,'2019-11-15 17:18:47','2019-11-15 17:18:47'),('1195270037005197313','1195268788138598401','添加',2,'role.add','role/add','/acl/role/form',NULL,NULL,0,'2019-11-15 17:19:19','2019-11-18 11:05:42'),('1195270442602782721','1195268788138598401','删除',2,'role.remove','','',NULL,NULL,0,'2019-11-15 17:20:55','2019-11-15 17:20:55'),('1195270621548568578','1195268788138598401','角色权限',2,'role.acl','role/distribution/:id','/acl/role/roleForm',NULL,NULL,0,'2019-11-15 17:21:38','2019-11-15 17:21:38'),('1195270744097742849','1195268893830864898','查看',2,'permission.list','','',NULL,NULL,0,'2019-11-15 17:22:07','2019-11-15 17:22:07'),('1195270810560684034','1195268893830864898','添加',2,'permission.add','','',NULL,NULL,0,'2019-11-15 17:22:23','2019-11-15 17:22:23'),('1195270862100291586','1195268893830864898','修改',2,'permission.update','','',NULL,NULL,0,'2019-11-15 17:22:35','2019-11-15 17:22:35'),('1195270887933009922','1195268893830864898','删除',2,'permission.remove','','',NULL,NULL,0,'2019-11-15 17:22:41','2019-11-15 17:22:41'),('1195349439240048642','1','讲师管理',1,NULL,'/edu/teacher','Layout',NULL,NULL,0,'2019-11-15 22:34:49','2019-11-15 22:34:49'),('1195349699995734017','1195349439240048642','讲师列表',1,NULL,'list','/edu/teacher/list',NULL,NULL,0,'2019-11-15 22:35:52','2019-11-15 22:35:52'),('1195349810561781761','1195349439240048642','添加讲师',1,NULL,'create','/edu/teacher/form',NULL,NULL,0,'2019-11-15 22:36:18','2019-11-15 22:36:18'),('1195349876252971010','1195349810561781761','添加',2,'teacher.add','','',NULL,NULL,0,'2019-11-15 22:36:34','2019-11-15 22:36:34'),('1195349979797753857','1195349699995734017','查看',2,'teacher.list','','',NULL,NULL,0,'2019-11-15 22:36:58','2019-11-15 22:36:58'),('1195350117270261762','1195349699995734017','修改',2,'teacher.update','edit/:id','/edu/teacher/form',NULL,NULL,0,'2019-11-15 22:37:31','2019-11-15 22:37:31'),('1195350188359520258','1195349699995734017','删除',2,'teacher.remove','','',NULL,NULL,0,'2019-11-15 22:37:48','2019-11-15 22:37:48'),('1195350299365969922','1','课程分类',1,NULL,'/edu/subject','Layout',NULL,NULL,0,'2019-11-15 22:38:15','2019-11-15 22:38:15'),('1195350397751758850','1195350299365969922','课程分类列表',1,NULL,'list','/edu/subject/list',NULL,NULL,0,'2019-11-15 22:38:38','2019-11-15 22:38:38'),('1195350500512206850','1195350299365969922','导入课程分类',1,NULL,'import','/edu/subject/import',NULL,NULL,0,'2019-11-15 22:39:03','2019-11-15 22:39:03'),('1195350612172967938','1195350397751758850','查看',2,'subject.list','','',NULL,NULL,0,'2019-11-15 22:39:29','2019-11-15 22:39:29'),('1195350687590748161','1195350500512206850','导入',2,'subject.import','','',NULL,NULL,0,'2019-11-15 22:39:47','2019-11-15 22:39:47'),('1195350831744782337','1','课程管理',1,NULL,'/edu/course','Layout',NULL,NULL,0,'2019-11-15 22:40:21','2019-11-15 22:40:21'),('1195350919074385921','1195350831744782337','课程列表',1,NULL,'list','/edu/course/list',NULL,NULL,0,'2019-11-15 22:40:42','2019-11-15 22:40:42'),('1195351020463296513','1195350831744782337','发布课程',1,NULL,'info','/edu/course/info',NULL,NULL,0,'2019-11-15 22:41:06','2019-11-15 22:41:06'),('1195351159672246274','1195350919074385921','完成发布',2,'course.publish','publish/:id','/edu/course/publish',NULL,NULL,0,'2019-11-15 22:41:40','2019-11-15 22:44:01'),('1195351326706208770','1195350919074385921','编辑课程',2,'course.update','info/:id','/edu/course/info',NULL,NULL,0,'2019-11-15 22:42:19','2019-11-15 22:42:19'),('1195351566221938690','1195350919074385921','编辑课程大纲',2,'chapter.update','chapter/:id','/edu/course/chapter',NULL,NULL,0,'2019-11-15 22:43:17','2019-11-15 22:43:17'),('1195351862889254913','1','统计分析',1,NULL,'/statistics/daily','Layout',NULL,NULL,0,'2019-11-15 22:44:27','2019-11-15 22:44:27'),('1195351968841568257','1195351862889254913','生成统计',1,NULL,'create','/statistics/daily/create',NULL,NULL,0,'2019-11-15 22:44:53','2019-11-15 22:44:53'),('1195352054917074946','1195351862889254913','统计图表',1,NULL,'chart','/statistics/daily/chart',NULL,NULL,0,'2019-11-15 22:45:13','2019-11-15 22:45:13'),('1195352127734386690','1195352054917074946','查看',2,'daily.list','','',NULL,NULL,0,'2019-11-15 22:45:30','2019-11-15 22:45:30'),('1195352215768633346','1195351968841568257','生成',2,'daily.add','','',NULL,NULL,0,'2019-11-15 22:45:51','2019-11-15 22:45:51'),('1195352547621965825','1','CMS管理',1,NULL,'/cms','Layout',NULL,NULL,0,'2019-11-15 22:47:11','2019-11-18 10:51:46'),('1195352856645701633','1195353513549205505','查看',2,'banner.list','',NULL,NULL,NULL,0,'2019-11-15 22:48:24','2019-11-15 22:48:24'),('1195352909401657346','1195353513549205505','添加',2,'banner.add','banner/add','/cms/banner/form',NULL,NULL,0,'2019-11-15 22:48:37','2019-11-18 10:52:10'),('1195353051395624961','1195353513549205505','修改',2,'banner.update','banner/update/:id','/cms/banner/form',NULL,NULL,0,'2019-11-15 22:49:11','2019-11-18 10:52:05'),('1195353513549205505','1195352547621965825','Bander列表',1,NULL,'banner/list','/cms/banner/list',NULL,NULL,0,'2019-11-15 22:51:01','2019-11-18 10:51:29'),('1195353672110673921','1195353513549205505','删除',2,'banner.remove','','',NULL,NULL,0,'2019-11-15 22:51:39','2019-11-15 22:51:39'),('1195354076890370050','1','订单管理',1,NULL,'/order','Layout',NULL,NULL,0,'2019-11-15 22:53:15','2019-11-15 22:53:15'),('1195354153482555393','1195354076890370050','订单列表',1,NULL,'list','/order/list',NULL,NULL,0,'2019-11-15 22:53:33','2019-11-15 22:53:58'),('1195354315093282817','1195354153482555393','查看',2,'order.list','','',NULL,NULL,0,'2019-11-15 22:54:12','2019-11-15 22:54:12'),('1196301740985311234','1195268616021139457','分配角色',2,'user.assgin','user/role/:id','/acl/user/roleForm',NULL,NULL,0,'2019-11-18 13:38:56','2019-11-18 13:38:56');

#
# Structure for table "acl_role"
#

CREATE TABLE `acl_role` (
`id` char(19) NOT NULL DEFAULT '' COMMENT '角色id',
`role_name` varchar(20) NOT NULL DEFAULT '' COMMENT '角色名称',
`role_code` varchar(20) DEFAULT NULL COMMENT '角色编码',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#
# Data for table "acl_role"
#

INSERT INTO `acl_role` VALUES ('1','普通管理员',NULL,NULL,0,'2019-11-11 13:09:32','2019-11-18 10:27:18'),('1193757683205607426','课程管理员',NULL,NULL,0,'2019-11-11 13:09:45','2019-11-18 10:25:44'),('1196300996034977794','test',NULL,NULL,0,'2019-11-18 13:35:58','2019-11-18 13:35:58');

#
# Structure for table "acl_role_permission"
#

CREATE TABLE `acl_role_permission` (
`id` char(19) NOT NULL DEFAULT '',
`role_id` char(19) NOT NULL DEFAULT '',
`permission_id` char(19) NOT NULL DEFAULT '',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_role_id` (`role_id`),
KEY `idx_permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限';

#
# Data for table "acl_role_permission"
#

INSERT INTO `acl_role_permission` VALUES ('1196301979754455041','1','1',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979792203778','1','1195268474480156673',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979821563906','1','1195268616021139457',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979842535426','1','1195269143060602882',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979855118338','1','1195269295926206466',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979880284161','1','1195269473479483394',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979913838593','1','1195269547269873666',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979926421506','1','1196301740985311234',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301979951587330','1','1195268788138598401',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980014501889','1','1195269821262782465',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980035473410','1','1195269903542444034',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980052250626','1','1195270037005197313',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980077416450','1','1195270442602782721',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980094193665','1','1195270621548568578',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980119359489','1','1195268893830864898',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980136136706','1','1195270744097742849',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980249382913','1','1195270810560684034',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980270354434','1','1195270862100291586',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980287131649','1','1195270887933009922',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980303908866','1','1195349439240048642',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980320686082','1','1195349699995734017',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980345851905','1','1195349979797753857',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980362629121','1','1195350117270261762',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980383600641','1','1195350188359520258',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980408766465','1','1195349810561781761',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980421349378','1','1195349876252971010',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980438126593','1','1195350299365969922',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980450709506','1','1195350397751758850',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980501041153','1','1195350612172967938',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980517818370','1','1195350500512206850',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980538789889','1','1195350687590748161',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980622675970','1','1195350831744782337',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980639453186','1','1195350919074385921',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980660424705','1','1195351159672246274',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980677201922','1','1195351326706208770',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980698173441','1','1195351566221938690',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980714950658','1','1195351020463296513',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980723339266','1','1195351862889254913',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980744310786','1','1195351968841568257',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980761088001','1','1195352215768633346',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980777865217','1','1195352054917074946',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980794642434','1','1195352127734386690',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980811419650','1','1195352547621965825',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980828196865','1','1195353513549205505',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980844974082','1','1195352856645701633',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980861751298','1','1195352909401657346',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980886917122','1','1195353051395624961',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980928860162','1','1195353672110673921',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980954025986','1','1195354076890370050',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980970803201','1','1195354153482555393',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196301980987580418','1','1195354315093282817',1,'2019-11-18 13:39:53','2019-11-18 13:39:53'),('1196305293070077953','1','1',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293099438081','1','1195268474480156673',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293120409602','1','1195268616021139457',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293153964034','1','1195269143060602882',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293183324162','1','1195269295926206466',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293212684290','1','1195269473479483394',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293237850114','1','1195269547269873666',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293271404545','1','1196301740985311234',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293485314049','1','1195268788138598401',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293506285569','1','1195269821262782465',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293527257089','1','1195269903542444034',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293552422914','1','1195270037005197313',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293565005825','1','1195270442602782721',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293594365954','1','1195270621548568578',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293611143169','1','1195268893830864898',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293627920385','1','1195270744097742849',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293657280513','1','1195349439240048642',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293674057729','1','1195349699995734017',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293690834946','1','1195349979797753857',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293716000770','1','1195350117270261762',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293736972290','1','1195350188359520258',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293749555202','1','1195349810561781761',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293766332417','1','1195349876252971010',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293795692546','1','1195350299365969922',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293812469762','1','1195350397751758850',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293837635586','1','1195350612172967938',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293858607106','1','1195350500512206850',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293875384322','1','1195350687590748161',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293892161538','1','1195350831744782337',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293950881794','1','1195350919074385921',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305293976047617','1','1195351159672246274',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294127042561','1','1195351326706208770',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294156402690','1','1195351566221938690',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294177374209','1','1195351862889254913',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294194151425','1','1195351968841568257',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294223511554','1','1195352215768633346',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294240288770','1','1195352054917074946',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294248677377','1','1195352127734386690',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294248677378','1','1195352547621965825',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294319980546','1','1195353513549205505',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294319980547','1','1195352856645701633',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294319980548','1','1195352909401657346',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294378700802','1','1195353051395624961',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294378700803','1','1195353672110673921',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294458392577','1','1195354076890370050',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294483558402','1','1195354153482555393',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305294500335618','1','1195354315093282817',1,'2019-11-18 13:53:03','2019-11-18 13:53:03'),('1196305566656139266','1','1',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566689693698','1','1195268474480156673',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566706470913','1','1195268616021139457',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566740025346','1','1195269143060602882',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566756802561','1','1195269295926206466',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566781968385','1','1195269473479483394',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566811328514','1','1195269547269873666',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566828105730','1','1196301740985311234',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566853271554','1','1195268788138598401',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566878437378','1','1195269821262782465',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566895214593','1','1195269903542444034',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566916186113','1','1195270037005197313',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566949740546','1','1195270442602782721',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566966517761','1','1195270621548568578',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305566991683585','1','1195268893830864898',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567012655106','1','1195270744097742849',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567029432322','1','1195270810560684034',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567042015233','1','1195270862100291586',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567100735490','1','1195270887933009922',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567117512705','1','1195349439240048642',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567142678530','1','1195349699995734017',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567155261442','1','1195349979797753857',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567172038658','1','1195350117270261762',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567188815873','1','1195350188359520258',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567218176001','1','1195349810561781761',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567234953217','1','1195349876252971010',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567251730434','1','1195350299365969922',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567272701954','1','1195350397751758850',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567289479170','1','1195350612172967938',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567310450690','1','1195350500512206850',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567327227905','1','1195350687590748161',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567448862722','1','1195350831744782337',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567478222850','1','1195350919074385921',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567495000065','1','1195351159672246274',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567520165889','1','1195351326706208770',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567541137409','1','1195351566221938690',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567570497538','1','1195351862889254913',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567587274754','1','1195351968841568257',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567604051970','1','1195352215768633346',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567633412098','1','1195352054917074946',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567683743745','1','1195352127734386690',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567721492481','1','1195352547621965825',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567742464002','1','1195353513549205505',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567771824129','1','1195352856645701633',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567792795650','1','1195352909401657346',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567809572866','1','1195353051395624961',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567843127298','1','1195353672110673921',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567868293122','1','1195354076890370050',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567885070338','1','1195354153482555393',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196305567910236162','1','1195354315093282817',1,'2019-11-18 13:54:08','2019-11-18 13:54:08'),('1196312702601695234','1','1',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702652026881','1','1195268474480156673',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702668804098','1','1195268616021139457',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702698164226','1','1195269143060602882',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702723330049','1','1195269295926206466',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702744301569','1','1195269473479483394',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702765273089','1','1195269547269873666',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702790438913','1','1196301740985311234',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702945628161','1','1195268788138598401',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312702970793985','1','1195269821262782465',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703000154114','1','1195269903542444034',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703025319938','1','1195270037005197313',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703046291458','1','1195270442602782721',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703063068673','1','1195270621548568578',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703084040193','1','1195268893830864898',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703113400321','1','1195270744097742849',0,'2019-11-18 14:22:29','2019-11-18 14:22:29'),('1196312703134371842','1','1195270810560684034',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703159537665','1','1195270862100291586',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703184703490','1','1195270887933009922',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703209869313','1','1195349439240048642',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703230840834','1','1195349699995734017',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703251812354','1','1195349979797753857',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703272783873','1','1195350117270261762',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703293755394','1','1195350188359520258',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703327309826','1','1195349810561781761',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703348281345','1','1195349876252971010',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703365058561','1','1195350299365969922',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703386030082','1','1195350397751758850',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703440556034','1','1195350612172967938',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703486693378','1','1195350500512206850',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703511859202','1','1195350687590748161',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703654465537','1','1195350831744782337',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703683825665','1','1195350919074385921',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703700602882','1','1195351159672246274',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703717380098','1','1195351326706208770',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703738351618','1','1195351566221938690',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703759323137','1','1195351020463296513',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703776100353','1','1195351862889254913',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703792877570','1','1195351968841568257',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703830626305','1','1195352215768633346',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703843209217','1','1195352054917074946',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703868375041','1','1195352127734386690',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703889346561','1','1195352547621965825',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703901929473','1','1195353513549205505',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703918706689','1','1195352856645701633',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703952261121','1','1195352909401657346',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703973232642','1','1195353051395624961',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312703990009857','1','1195353672110673921',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312704048730114','1','1195354076890370050',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312704069701633','1','1195354153482555393',0,'2019-11-18 14:22:30','2019-11-18 14:22:30'),('1196312704094867457','1','1195354315093282817',0,'2019-11-18 14:22:30','2019-11-18 14:22:30');

#
# Structure for table "acl_user"
#

CREATE TABLE `acl_user` (
`id` char(19) NOT NULL COMMENT '会员id',
`username` varchar(20) NOT NULL DEFAULT '' COMMENT '微信openid',
`password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
`nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
`salt` varchar(255) DEFAULT NULL COMMENT '用户头像',
`token` varchar(100) DEFAULT NULL COMMENT '用户签名',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

#
# Data for table "acl_user"
#

INSERT INTO `acl_user` VALUES ('1','admin','96e79218965eb72c92a549dd5a330112','admin','',NULL,0,'2019-11-01 10:39:47','2019-11-01 10:39:47'),('2','test','96e79218965eb72c92a549dd5a330112','test',NULL,NULL,0,'2019-11-01 16:36:07','2019-11-01 16:40:08');

#
# Structure for table "acl_user_role"
#

CREATE TABLE `acl_user_role` (
`id` char(19) NOT NULL DEFAULT '' COMMENT '主键id',
`role_id` char(19) NOT NULL DEFAULT '0' COMMENT '角色id',
`user_id` char(19) NOT NULL DEFAULT '0' COMMENT '用户id',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_role_id` (`role_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

#
# Data for table "acl_user_role"
#

INSERT INTO `acl_user_role` VALUES ('1','1','2',0,'2019-11-11 13:09:53','2019-11-11 13:09:53');

创建工程

父工程需要加<packaging>pom</packaging>

最后创建出来的工程应该是这个样子的

最外边的acl_parent的父工程boot版本指定成2.2.1.RELEASE

1
2
3
4
5
6
7
8
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu</groupId>
<artifactId>acl_parent</artifactId>

导入依赖

acl_parent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.0.5</mybatis-plus.version>
<velocity.version>2.0</velocity.version>
<swagger.version>2.7.0</swagger.version>
<jwt.version>0.7.0</jwt.version>
<fastjson.version>1.2.28</fastjson.version>
<gson.version>2.8.2</gson.version>
<json.version>20170516</json.version>
<cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

service

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

<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>


<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

service_acl

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>spring_security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>

api_gateway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>

common

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>

<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>

<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided </scope>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>

spring_security

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>

启动Redis和Nacos

1
2
3
4
5
6
7
[root@xiamu ~]# redis-server /etc/redis.config 
[root@xiamu ~]# ps -ef | grep redis
root 2975 1 0 15:56 ? 00:00:00 redis-server *:6379
root 2991 2856 0 15:56 pts/0 00:00:00 grep --color=auto redis
[root@xiamu ~]# redis-cli
127.0.0.1:6379>

启动完成之后使用 Redis Desktop Manager 测试一下能够连接上

nacos在linux没有安装, 直接上windows用了

1
startup.cmd -m standalone

引入工具类

GlobalExceptionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

//指定出现什么异常执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody //为了返回数据
public R error(Exception e) {
e.printStackTrace();
return R.error().message("执行了全局异常处理..");
}

//特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody //为了返回数据
public R error(ArithmeticException e) {
e.printStackTrace();
return R.error().message("执行了ArithmeticException异常处理..");
}

//自定义异常
@ExceptionHandler(GuliException.class)
@ResponseBody //为了返回数据
public R error(GuliException e) {
log.error(e.getMessage());
e.printStackTrace();

return R.error().code(e.getCode()).message(e.getMsg());
}

}

GuliException.java

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor //生成有参数构造方法
@NoArgsConstructor //生成无参数构造
public class GuliException extends RuntimeException {
private Integer code;//状态码
private String msg;//异常信息
}

MyMetaObjectHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//属性名称,不是字段名称
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}

MD5.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public final class MD5 {

public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}

public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}

}

R.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//统一返回结果的类
@Data
public class R {

private Boolean success;

private Integer code;

private String message;

private Map<String, Object> data = new HashMap<String, Object>();

//把构造方法私有
private R() {}

//成功静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(20000);
r.setMessage("成功");
return r;
}

//失败静态方法
public static R error() {
R r = new R();
r.setSuccess(false);
r.setCode(20001);
r.setMessage("失败");
return r;
}

public R success(Boolean success){
this.setSuccess(success);
return this;
}

public R message(String message){
this.setMessage(message);
return this;
}

public R code(Integer code){
this.setCode(code);
return this;
}

public R data(String key, Object value){
this.data.put(key, value);
return this;
}

public R data(Map<String, Object> map){
this.setData(map);
return this;
}
}

ResponseUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ResponseUtil {

public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}

RedisConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}

SwaggerConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {

@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
//.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();

}

private ApiInfo webApiInfo(){

return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("java", "http://atguigu.com", "1123@qq.com"))
.build();
}
}

编写security工具类

编写DefaultPasswordEncoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

public DefaultPasswordEncoder() {
this(-1);
}

public DefaultPasswordEncoder(int strength) {

}

// 进行MD5加密
@Override
public String encode(CharSequence charSequence) {
return MD5.encrypt(charSequence.toString());
}

// 进行密码比对
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(MD5.encrypt(charSequence.toString()));
}
}

编写TokenManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class TokenManager {
// token 有效时长
private Long tokenExpiration = 24*60*60*1000L;

// 编码秘钥
private String tokenSignKey = "123456";

// 1. 使用jwt根据用户名生成token
public String createToken(String username) {
String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}

// 2 根据token字符串得到用户信息
public String getUserInfoFromToken(String token) {
String userInfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return userInfo;
}

// 3 删除token
public void removeToken(String token) {}

}

编写TokenLogoutHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 退出处理器
public class TokenLogoutHandler implements LogoutHandler {

private TokenManager tokenManager;
private RedisTemplate redisTemplate;

public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}

@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
// 1. 从header里面获取token
// 2. token不为空, 移除token, 从redis删除token
String token = httpServletRequest.getHeader("token");
if (token != null) {
// 移除
tokenManager.removeToken(token);

// 从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(username);
}
ResponseUtil.out(httpServletResponse, R.ok());
}
}

编写UnauthorizedEntryPoint

1
2
3
4
5
6
7
8
9
// 未授权统一处理类
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}

编写SecurityUser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List<String> permissionValueList;

public SecurityUser() {
}

public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
if (StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new
SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}

@Override
public String getPassword() {
return currentUserInfo.getPassword();
}

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

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

编写User

1
2
3
4
5
6
7
8
9
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private String username;
private String password;
private String nickName;
private String salt;
private String token;
}

编写TokenLoginFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 认证过滤器
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;

public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}

// 1. 获取表单提交用户名和密码
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 获取表单提交数据
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}

// 2. 认证成功调用方法
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 认证成功, 得到认证成功之后用户信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
// 根据用户名生成token
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
// 把用户名生成token
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
// 返回token
ResponseUtil.out(response, R.ok().data("token", token));
}

// 3. 认证失败调用方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}

编写TokenAuthFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class TokenAuthFilter extends BasicAuthenticationFilter {

private TokenManager tokenManager;
private RedisTemplate redisTemplate;

public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
// 判断如果有权限信息, 放到权限上下文中
if (authenticationToken != null) {
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// 从header获取token
String token = request.getHeader("token");
if (token != null) {
// 从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);

// 从redis获取对应权限列表
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(username);
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permissionValue);
authorities.add(simpleGrantedAuthority);
}
return new UsernamePasswordAuthenticationToken(username, token, authorities);
}
return null;
}
}

编写TokenWebSecurityConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private DefaultPasswordEncoder defaultPasswordEncoder;
private UserDetailsService userDetailsService;

@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService,
DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager,
RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint()) // 没有访问权限
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout") // 退出路径
.addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate))
.and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
.addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate))
.httpBasic();
}

// 调用userDetailsService和密码处理
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}

// 不进行认证的路径, 可以直接访问
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");
}
}

编写UserDetailService

复制aclservice模块, 太多了, 就不想粘贴了QAQ

代码仓库: https://gitee.com/xialonggui/spring-security

编写UserDetailService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class UserDetailServiceImpl implements UserDetailsService {

@Autowired
private UserService userService;

@Autowired
private PermissionService permissionService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询数据
User user = userService.selectByUsername(username);

// 判断
if (user == null) {
throw new UsernameNotFoundException("用户不存在! ");
}

com.atguigu.entity.User curUser = new com.atguigu.entity.User();
BeanUtils.copyProperties(user, curUser);

// 根据用户查询用户权限列表
List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser = new SecurityUser();
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(permissionValueList);
return securityUser;
}
}

整合网关

启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}

解决跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class CorsConfig {

// 解决跨域
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(
new PathPatternParser());
source.registerCorsConfiguration("/**", corsConfiguration);

return new CorsWebFilter(source);
}
}

整合前端

修改dev.env.js

安装node-sass

1
2
npm install --save node-sass
npm run start

这里用时的node 版本是14版本
node -v
v14.21.3


SpringSecurity
https://xiamu.icu/Java/SpringSecurity/
作者
肉豆蔻吖
发布于
2023年5月21日
许可协议