一、基本环境搭建

父pom依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
</parent>

1. 添加pom依赖:

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

2. 创建测试用Controller

@RestController
public class TestController {

    @GetMapping("getData")
    public String getData() {
        return "date";
    }

}

3. 创建SpringBoot启动类并run

@SpringBootApplication
public class SpringBootTestApplication {

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

}

4. 测试

访问http://127.0.0.1:8080/getData,由于我们开启了SpringSecurity且当前是未登录状态,页面会被302重定向到http://127.0.0.1:8080/login,页面如下:

SpringBoot安全认证Security

用户名:user,密码可以在控制台输出中找到:

SpringBoot安全认证Security

输入正确的用户名和密码后点击Login按钮即被重新302到http://127.0.0.1:8080/getData并显示查询数据:

SpringBoot安全认证Security

这表示我们的接口已经被spring保护了。

那么肯定会有小伙伴吐槽了,这么复杂的密码,鬼才及得住,所以...

二、为Spring Security设定用户名和密码

为了解决复杂密码的问题,我们可以在application.yml中做如下设定:

spring:
  security:
    user:
      name: user
      password: 123

这样我们就可以通过用户名user密码123来访问http://127.0.0.1:8080/getData接口了。

然后肯定又有小伙伴吐槽了,整个系统就一个用户么?有哪个系统是只有一个用户的?所以...

三、为Spring Security设定多个用户

如果想要给Spring Security设定多个用户可用,则新建一个class:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig { @Bean public UserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); InMemoryUserDetailsManager manager
= new InMemoryUserDetailsManager(); manager.createUser(User
.withUsername(
"admin")
.password("admin")
.passwordEncoder(encoder::encode)
.roles("")
.build()
); manager.createUser(User
.withUsername(
"guest")
.password("guest")
.passwordEncoder(encoder::encode)
.roles("")
.build()
);
return manager; } }
  1. 注意需要注解@EnableWebSecurity
  2. InMemoryUserDetailsManager:顾名思义,将用户名密码存储在内存中的用户管理器。我们通过这个管理器增加了两个用户,分别是:用户名admin密码admin,用户名guest密码guest。

做完如上更改后重启应用,再次访问http://127.0.0.1:8080/getData,输入admin/admin或guest/guest即可通过身份验证并正常使用接口了。

看到这肯定又有小伙伴要吐槽了:用户数据直接硬编码到代码里是什么鬼!我要把用户放在数据库!所以...

四、SpringSecurity+Mysql

想要使用数据库,那么我们可以

1. 增加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2. 配置数据库连接

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.2.12:3306/test?characterEncoding=utf8
    username: root
    password: onceas

3. 创建测试用表结构及数据

drop table if exists test.user;
create table test.user (
  id int auto_increment primary key,
  username varchar(50),
  password varchar(50)
);

insert into test.user(id, username, password) values (1, 'admin', 'admin');
insert into test.user(id, username, password) values (2, 'guest', 'guest');

我们创建了用户信息表,并插入两个用户信息,用户名/密码依然是admin/admin、guest/guest

4. entity、dao、service

public class User {

    private int id;
    private String username;
    private String password;

    // get set ...
}
@Repository
public class LoginDao {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public LoginDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<User> getUserByUsername(String username) {
        String sql = "select id, username, password from user where username = ?";
        return jdbcTemplate.query(sql, new String[]{username}, new BeanPropertyRowMapper<>(User.class));
    }
}
@Service
public class LoginService {

    private final LoginDao loginDao;

    @Autowired
    public LoginService(LoginDao loginDao) {
        this.loginDao = loginDao;
    }

    public List<User> getUserByUsername(String username) {
        return loginDao.getUserByUsername(username);
    }

}

5. 调整WebSecurityConfig

@Bean
public UserDetailsService userDetailsService() {
    return username -> {
        List<UserEntity> users = loginService.getUserByUsername(username);
        if (users == null || users.size() == 0) {
            throw new UsernameNotFoundException("用户名未找到");
        }
        String password = users.get(0).getPassword();
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        String passwordAfterEncoder = passwordEncoder.encode(password);
        return User.withUsername(username).password(passwordAfterEncoder).roles("").build();
    };
}

做完如上更改后重启应用,再次访问http://127.0.0.1:8080/getData,输入admin/admin或guest/guest即可通过身份验证并正常使用接口了。

关于UserDetailsService,有些东西要说明下:

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String passwordAfterEncoder = passwordEncoder.encode(password);

上面这两句代码是在对用户密码进行加密。为什么要这样子呢?看到这肯定又有小伙伴会吐槽:数据库存储铭文密码是什么鬼!对,Spring也是尽量在帮助开发者避免这个事情。所以SpringSecurity在进行密码比对的时候需要开发者提供加密后的密码。我们上面的写法其实是不合理的,实际情况应该是数据库中存储密文密码,然后将数据库中的密码直接传给User.password()就可以了。

6. 关于SpringSecurity加密后的密文格式

我们可以通过打断点的方式或者增加

System.out.println(username + "---->>>" + passwordAfterEncoder);

来查看下,如果admin/admin被登录时候,passwordAfterEncoder的值是什么?输出结果:

admin---->>>{bcrypt}$2a$10$d4VkiIfP7MyNSipjLtQ0Keva4ST6U6Fnw77iiv39IGnGswptqWRG.
guest---->>>{bcrypt}$2a$10$8jRMbiGzFIS4GU3SWAm83eWgFO29EEb5QhXOEkPEaabw5Oiy/jxUC

可以看出加密后的密码可以分为两部分

  1. {}内描述了加密算法,这里为bcrypt算法。
  2. {}后面即为密文密码,这里是包含盐的。

所以SpringSecurity的工作原理就是:当用户输入用户名和密码点击Login以后,SpringSecurity先通过调用我们自定义的UserDetailsService获取到加密后密码,然后根据{}里的内容获知加密算法,再将用户输入的密码按照该算法进行加密,最后再与{}后的密文密码比对即可获知用户凭据是否有效。

通过查看PasswordEncoderFactories的源码,我们可以知道SpringEncoder工具可以提供哪些加密算法:

public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }

其中LdapShaPasswordEncoder、Md4PasswordEncoder、MessageDigestPasswordEncoder、NoOpPasswordEncoder、StandardPasswordEncoder已经不建议使用了。SpringSecurity认为:

Digest based password encoding is not considered secure.  //基于摘要的密码编码被认为是不安全的

五 、权限控制

以上内容我们只解决了用户登录问题,但是实际开发中仅仅完成用户登录是不够的,我们还需要用户授权及授权验证。由于我们已经将用户信息存储到数据库里了,那么姑且我们也将权限信息存储在数据库吧。

1. 准备数据库表及测试数据

drop table if exists test.role;
create table test.role (
  id int auto_increment primary key,
  role varchar(50)
);

drop table if exists test.permission;
create table test.permission (
  id int auto_increment primary key,
  permission varchar(50)
);

drop table if exists test.user_r_role;
create table test.user_r_role (
  userid int,
  roleid int
);

drop table if exists test.role_r_permission;
create table test.role_r_permission (
  roleid int,
  permissionid int
);

drop table if exists test.user_r_permission;
create table test.user_r_permission (
  userid int,
  permissionid int
);

insert into test.role(id, role) values (1, 'adminRole');
insert into test.role(id, role) values (2, 'guestRole');

insert into test.permission(id, permission) values (1, 'permission1');
insert into test.permission(id, permission) values (2, 'permission2');
insert into test.permission(id, permission) values (3, 'permission3');
insert into test.permission(id, permission) values (4, 'permission4');

insert into test.user_r_role(userid, roleid) values (1, 1);
insert into test.user_r_role(userid, roleid) values (2, 2);

insert into test.role_r_permission(roleid, permissionid) values (1, 1);
insert into test.role_r_permission(roleid, permissionid) values (1, 2);

insert into test.user_r_permission(userid, permissionid) values (1, 3);
insert into test.user_r_permission(userid, permissionid) values (1, 4);
insert into test.user_r_permission(userid, permissionid) values (2, 3);
insert into test.user_r_permission(userid, permissionid) values (2, 4);
View Code

相关文章:

  • 2021-07-23
  • 2021-04-25
  • 2022-12-23
  • 2021-05-17
  • 2021-10-20
  • 2021-07-19
  • 2022-12-23
  • 2021-09-20
猜你喜欢
  • 2021-09-23
  • 2022-02-08
  • 2022-12-23
  • 2023-02-25
  • 2021-05-23
  • 2021-05-04
  • 2022-12-23
相关资源
相似解决方案