新建 Markdown
准备 sql
create table users(
id bigint primary key auto_increment,
username varchar(20) unique not null,
password varchar(100)
);
-- 密码 123
insert into users values(1,'Tom','123');
-- 密码 321
insert into users values(2,'Jerry','321');
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency><!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--lombok 用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
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
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
application.yaml
# 配置数据库连接信息
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://security?serverTimezone=GMT%2B8
username: root
password: 1234
1
2
3
4
5
6
7
2
3
4
5
6
7
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class User {
private Integer id;
private String username;
private String password;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
1
2
3
2
3
UserDetailsService
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* 作用: 进行用户名的判断
* 运行流程: 这个方法会传入一个用户名, 我们可以根据用户名进行查询, 并将查询结果交给SpringSecurity,
* SpringSecurity会帮我们进行密码的判断操作, 我们需要负责用户名的判断。
* 总结: SpringSecurity会根据我们返回的UserDetails对象进行密码的判断, 但是并不会判断用户名,
* 因此我们需要编写用户名的判断操作
* @param username 登录时传入的用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用userMapper方法, 根据用户名查询数据库
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
// 判断
if(user == null){
throw new UsernameNotFoundException("用户不存在");
}
// 设置用户拥有的权限, 多个权限之间使用逗号分割
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new org.springframework.security.core.userdetails.User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()), // BCryptPasswordEncoder在对密码进行对比时, 一定是拿明文和密文进行对别, 所有我们这里进行手动加密, 因为我们数据库中存的就是明文, 如果数据库中存的是密文就不需要我们手动进行加密了;
auths);
}
}
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
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
SecurityConfig
/**
* 1.创建一个配置类继承自WebSecurityConfigurerAdapter
* 2.重写configure(AuthenticationManagerBuilder auth)方法, 给auth传入一个UserDetailsService接口的实现类
* 3.编写一个UserDetailsService的实现类, 重写loadUserByUsername方法, 进行用户名判断, 并返回UserDetails对象交给SpringSecurity进行密码的判断
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
基于数据库记住我
原理
SpringSecurity 实现自动登录是基于 Cookie技术,但是 Cookie 是存在浏览器中的,用 Cookie 存储密码是不安全的;因此第一次登入时 SpringSecurity 通过 Cookie 存入加密且唯一的唯一标识,再向数据库中添加一条数据 ( 包括唯一标识和用户信息字段 )
再次访问会获取到这个 Cookie 信息,通过这个信息查询数据库中对应的数据,如果查询到用户信息直接进行登录。
创建表
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
注意:表结构必须一模一样,包括表名。也可以让 SpringSecurity 自动创建。
// JdbcTokenRepositoryImpl类中的部分属性
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {
/** Default SQL for creating the database table to store the tokens */
public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
+ "token varchar(64) not null, last_used timestamp not null)";
/** The default SQL used by the <tt>getTokenBySeries</tt> query */
public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
/** The default SQL used by <tt>createNewToken</tt> */
public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
/** The default SQL used by <tt>updateToken</tt> */
public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";
/** The default SQL used by <tt>removeUserTokens</tt> */
public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";
private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL;
private String insertTokenSql = DEF_INSERT_TOKEN_SQL;
private String updateTokenSql = DEF_UPDATE_TOKEN_SQL;
private String removeUserTokensSql = DEF_REMOVE_USER_TOKENS_SQL;
private boolean createTableOnStartup;
}
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
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
可以看到 JdbcTokenRepositoryImpl
类中定义了默认的 建表
、CRUD
的 SQL 语句,SpringSecurity
就是根据这个类实现自动登录中的数据库操作的。
编写自动登录配置类
package org.hong.springsecurity01.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
public class BrowserSecurityConfig {
// 注入数据源
@Autowired
private DataSource dataSource;
/**
* PersistentTokenRepository组件用于创建、保存、修改、删除Token的操作
* SpringSecurity会使用此组件来实现rememberme功能
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 注入数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,之后执行就要删除掉, 不然报错!
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
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
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
修改安全配置类
修改 configure(HttpSecurity http)
方法
// 自动注入persistentTokenRepository
@Autowired
private PersistentTokenRepository persistentTokenRepository;
http.rememberMe()
.tokenRepository(persistentTokenRepository) // 开启自动登录功能
.tokenValiditySeconds(60)// 设置有效时长, 单位秒; 默认为两周
.userDetailsService(userDetailsService); // 绑定userDetailsServic, 自动登录也会运行这个service, 绑定用户的权限、角色...
1
2
3
4
5
6
7
2
3
4
5
6
7
页面添加记住我复选框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<!-- name值必须为remember-me -->
<input type="checkbox" name="remember-me">自动登录<br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:自动登录的选项的 name 值必须为 remember-me
。
修改remember-me功能提交的的name值
http.rememberMe()
.rememberMeParameter("remember");
1
2
2
自定义用户登录页面
引入登录页面
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:表单的提交方式必须是 POST
,用户名和密码必须是 username
和 password
。
原理:
在执行登录时会经过 UsernamePasswordAuthenticationFilter
过滤器,下面是部分属性。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
// 这个属性设置了登录页默认的访问路径\\login和访问方式POST
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST");
// 使用这个参数获取用户名
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
// 使用这个参数获取密码
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
自定义用户名和密码的name
SecurityConfig 配置类新增方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.usernameParameter("pwd") // 设置获取登录名时的依据
.passwordParameter("user"); // 设置获取密码时的依据
}
1
2
3
4
5
6
2
3
4
5
6
设置未授权的请求跳转到登录页
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
// 注意: 在设置了登录表单的url和可以直接访问的url后, 如果不进一步调用permitAll()方法, 则只是将这些url设置进去了,
// 但是还是需要权限才能访问, 而想要访问就需要登录, 但是访问登录页面页面需要权限, 就会造成尴尬的情况。
// 因此在设置了某些登录表单的url必须进一步调用permitAll()方法。
// permitAll(): 使用permitAll()将配置授权,以便在该特定路径上允许所有请求
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登录表单的设置
http.formLogin()
.loginPage("/login.html") // 配置登录页的url
.loginProcessingUrl("/user/login") // 设置提交登录的url, 我们不需要编写这个url对应的控制器
.defaultSuccessUrl("/index") // 登录成功的跳转路径, redirect方式; successForwardUrl()方法为forword方式
.failureUrl("/hello") // 登录失败的跳转路径, redirect方式; failureForwardUrl()方法为forword方式
.permitAll(); // 将这些路径授予访问权限
// 设置可以直接访问的请求
http.authorizeRequests()
.antMatchers("/", "/hello", "/user/login") // 表示请求路径
.permitAll() // 将这些路径授予访问权限
// 设置其余请求都需要进行登录
http.authorizeRequests()
.anyRequest() // 其他请求
.authenticated(); // 需要认证
// 关闭csrf防护, 可以先理解为防火墙
http.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
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
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
Controller
@RestController
public class TestController {
@GetMapping("/hello")
public String hello(){
return "Hello Security!";
}
@GetMapping("/index")
public String index(){
return "hello index!";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
运行项目,进行测试,测试成功!!!
上次更新: 2024/03/29, 08:56:31