SpringBoot整合SpringSecurity(通俗易懂)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: SpringBoot整合SpringSecurity(通俗易懂)

基于数据库的身份认证

一、创建项目

创建一个 SpringBoot 模块项目,选择相关依赖:

先搭建项目正常访问,在pom.xml中,先把Spring Security依赖注释

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


在MySQL数据库中创建一张用户表,id主键自增,并添加两个用户如下:

代码:

创建entity实体

在MySQL数据库中创建一张用户表,id主键自增,并添加两个用户如下:

代码:

创建entity实体

1. @Data
2. public class UserInfo {
3.     private int id;
4.     private String username;
5.     private String password;
6.     private String role;
7. }


创建mapper接口

1. @Mapper
2. @Repository
3. public interface UserInfoMapper {
4.     @Select("select * from user where username = #{username}")
5.     UserInfo getUserInfoByUsername(String username);
6. }
7. 创建UserInfoService.java文件
8. 
9. @Service
10. public class UserInfoService {
11.     @Autowired
12.     private UserInfoMapper userInfoMapper;
13. 
14.     public UserInfo getUserInfo(String username){
15.         return userInfoMapper.getUserInfoByUsername(username);
16.     }
17. }

创建HelloController.java文件

1. @RestController
2. public class HelloController {
3.     @Autowired
4.     private UserInfoService userInfoService;
5.     
6.     @GetMapping("/get-user")
7.     public UserInfo getUser(@RequestParam String username){
8.         return userInfoService.getUserInfoByUsername(username);
9.     }
10. }


配置文件application.yml中设置MySQL连接配置和MyBatis配置

1. spring:
2.   datasource:
3.     url: jdbc:mysql://localhost:3306/security?useSSL=FALSE&serverTimezone=UTC
4.     username: root
5.     password: root
6.     driver-class-name: com.mysql.cj.jdbc.Driver
7. logging:
8.   level:
9.     com.example.bdatabaserole.mapper: debug  # 打印sql语句

以上配置好后,启动项目,访问localhost:8080/get-user?username=user,正常获取到用户信息:

下面开始使用Spring Security安全验证:

二、Spring Security基于数据库认证

把pom.xml中的Spring Security依赖注释去掉:

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


要从数据库读取用户信息进行身份认证,需要新建类实现UserDetailService接口重写loadUserByUsername方法:

1. @Component
2. public class CustomUserDetailsService implements UserDetailsService {
3.     @Autowired
4.     private UserInfoService userInfoService;
5.     
6.     /**
7.      * 需新建配置类注册一个指定的加密方式Bean,或在下一步Security配置类中注册指定
8.      */
9.     @Autowired
10.     private PasswordEncoder passwordEncoder;
11. 
12.     @Override
13.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
14.         // 通过用户名从数据库获取用户信息
15.         UserInfo userInfo = userInfoService.getUserInfo(username);
16.         if (userInfo == null) {
17.             throw new UsernameNotFoundException("用户不存在");
18.         }
19. 
20.         // 得到用户角色
21.         String role = userInfo.getRole();
22. 
23.         // 角色集合
24.         List<GrantedAuthority> authorities = new ArrayList<>();
25.         // 角色必须以`ROLE_`开头,数据库中没有,则在这里加
26.         authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
27. 
28.         return new User(
29.                 userInfo.getUsername(),
30.              // 因为数据库是明文,所以这里需加密密码
31.                 passwordEncoder.encode(userInfo.getPassword()),
32.                 authorities
33.         );
34.     }
35. }


创建Security的配置类WebSecurityConfig继承WebSecurityConfigurerAdapter,并重写configure(auth)方法:

1. @EnableWebSecurity
2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
3.     @Autowired
4.     private MyUserDatailService userDatailService;
5. 
6.     /**
7.      * 指定加密方式
8.      */
9.     @Bean
10.     public PasswordEncoder passwordEncoder(){
11.         // 使用BCrypt加密密码
12.         return new BCryptPasswordEncoder();
13.     }
14. 
15.     @Override
16.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
17.          auth
18.                  // 从数据库读取的用户进行身份认证
19.                 .userDetailsService(userDatailService)
20.                 .passwordEncoder(passwordEncoder());
21.     }
22. }

上面设置完后,重新启动,在登录页面就可以输入数据库中的用户名/密码了。

三、角色访问控制

上面设置后,可以使用数据库中的用户名/密码登录,还获取到了用户的角色。通过用户的角色,可以限制用户的请求访问:

开启方法的访问权限,需要在WebSecurityConfig添加

@EnableGlobalMethodSecurity注解

1. @EnableWebSecurity
2. @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全验证
3. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
4.     ...
5. }


修改UserController.java类,增加方法的访问权限

1. @RestController
2. public class HelloController {
3.     @Autowired
4.     private UserInfoService userInfoService;
5. 
6.     @GetMapping("/get-user")
7.     public UserInfo getUser(@RequestParam String username){
8.         return userInfoService.getUserInfo(username);
9.     }
10. 
11.     @PreAuthorize("hasAnyRole('user')") // 只能user角色才能访问该方法
12.     @GetMapping("/user")
13.     public String user(){
14.         return "user角色访问";
15.     }
16. 
17.     @PreAuthorize("hasAnyRole('admin')") // 只能admin角色才能访问该方法
18.     @GetMapping("/admin")
19.     public String admin(){
20.         return "admin角色访问";
21.     }
22. }

配置完后,重新启动程序,在登录页面中:

输入user/user123登录,该用户角色为user,可以访问localhost:8080/user,不能访问localhost:8080/admin;

再重启程序输入admin/admin123登录,角色为admin,能访问localhost:8080/admin,不能访问localhost:8080/user。

四、密码加密保存

前面的用户密码都是手动添加的,所以数据库中是明文显示,在实际开发中,都是需要加密保存的。

下面模拟简单注册用户,加密保存密码:

UserInfoMapper.java类中添加插入用户

1. @Mapper
2. @Repository
3. public interface UserMapper {
4.  ...
5.     // 插入用户
6.     @Insert("insert into user(username, password, role) value(#{username}, #{password}, #{role})")
7.     int insertUserInfo(UserInfo userInfo);
8. }

UserInfoService.java类中添加插入方法,注意要加密密码

1. @Service
2. public class UserInfoService {
3.     ...
4.     
5.     @Autowired
6.     private PasswordEncoder passwordEncoder;
7. 
8.     ...
9. 
10.     public int insertUser(UserInfo userInfo){
11.         // 加密密码
12.         userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
13.         return userInfoMapper.insertUserInfo(userInfo);
14.     }
15. 
16. }

修改HelloController.java,增加添加用户接口

1. @RestController
2. public class HelloController {
3.     ...
4.         
5.     @PostMapping("/add-user")
6.     public int addUser(@RequestBody UserInfo userInfo){
7.         return userInfoService.insertUser(userInfo);
8.     }
9. }


配置完后,启动服务,使用Postman发送POST请求来添加用户:

点击Send按钮后,添加失败,不会返回成功1,看到红框的状态码显示401 Unauthorized,说明无权限,需要登录,但注册用户是不用登录的,所以就需要注册用户的请求无需身份验证:

修改WebSecurityConfig配置类,重写configure(HttpSecurity http)方法,配置允许注册用户的请求访问:

1. @EnableWebSecurity
2. @EnableGlobalMethodSecurity(prePostEnabled = true)
3. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
4.  ...    
5.     @Override
6.     protected void configure(HttpSecurity http) throws Exception {
7.         http
8.                 .authorizeRequests()
9.                 .antMatchers(HttpMethod.POST, "/add-user").permitAll() // 允许post请求/add-user,而无需认证
10.                 .anyRequest().authenticated() // 所有请求都需要验证
11.                 .and()
12.                 .formLogin() // 使用默认的登录页面
13.                 .and()
14.                 .csrf().disable();// post请求要关闭csrf验证,不然访问报错;实际开发中开启,需要前端配合传递其他参数
15.     }
16. }


配置允许POST请求/add-user访问后,再在Postman发送请求就可以成功了:

图片

查看数据库数据,添加的用户密码已加密:

使用加密密码登录,需要修改CustomUserDetailsService类,之前从数据库拿到明文密码后需要加密,现在数据库里面的密码已经加密了,就不用加密了:

1. @Component
2. public class MyUserDatailService implements UserDetailsService {
3.     //@Autowired
4.     //private PasswordEncoder passwordEncoder;
5.     ...
6.     @Override
7.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
8.         ...
9.         return new User(
10.                 user.getUsername(),
11.                 // 数据库密码已加密,不用再加密
12.                 user.getPassword(),
13.                 authorities
14.         );
15.     }
16. }

浏览器访问localhost:8080/user,输入zeng/zeng123登录即可。

五、密码修改

UserInfoMapper.java类中添加更新用户密码操作:

1. @Mapper
2. @Repository
3. public interface UserMapper {
4.  ...
5.  @Update("update user set password = #{newPwd} where username = #{username}")
6.     int updatePwd(String username, String newPwd);
7. }
8. UserInfoService.java类中添加更新密码的操作方法:
9. 
10. @Service
11. public class UserInfoService { 
12.     ...
13.     public int updatePwd(String oldPwd, String newPwd) {
14.         // 获取当前登录用户信息(注意:没有密码的)
15.         UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
16.         String username = principal.getUsername();
17. 
18.         // 通过用户名获取到用户信息(获取密码)
19.         UserInfo userInfo = userInfoMapper.getUserInfoByUsername(username);
20. 
21.         // 判断输入的旧密码是正确
22.         if (passwordEncoder.matches(oldPwd, userInfo.getPassword())) {
23.             // 不要忘记加密新密码
24.             return userInfoMapper.updatePwd(username, passwordEncoder.encode(newPwd));
25.         }
26.         return 0;
27.     }
28. }

HelloController.java类增加修改用户密码接口:

1. @RestController
2. public class HelloController {    
3.     @PutMapping("/updatePwd")
4.     public int updatePwd(@RequestBody Map<String, String> map){
5.         return userInfoService.updatePwd(map.get("oldPwd"), map.get("newPwd"));
6.     }
7. }


启动程序,因为需要登录,且修改密码请求方式为PUT请求,所以无法使用Postman发起请求,可以使用谷歌浏览器的插件Restlet Client:

先浏览器输入http://localhost:8080/login登录用户zeng/zeng123:

在Restlet Client中进行PUT更新请求:

这里更新后,需要重启后就可以使用新密码登录了。

5.用户角色多对多关系

上面的设置后,能基本实现了身份认证和角色授权了,但还是有一点不足:

我们前面用户表中,用户和角色是绑定一起,用户就只有一个角色了,但实际上,用户可能拥有多个角色,角色拥有多个用户,是多对多的关系,所以需要重新设置用户表和角色表。

创建普通项目运行

IDEA创建一个 Spring Initializr模块项目,名为b-database-manytomany-role,选择Spring Security和web依赖,其他按需选择Lombok、MySQL、JDBC和MyBatis。

先按照普通项目创建起来正常访问,在pom.xml中,先把Spring Security依赖注释

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


新建表,主键id全部都是自增。

用户user2表

1. CREATE TABLE user2 ( uid int(11) NOT NULL AUTO_INCREMENT,
2. username varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci
3. NULL DEFAULT NULL, password varchar(255) CHARACTER SET utf8
4. COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (uid) USING
5. BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8
6. COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

角色role2表,指定了2个角色

1. CREATE TABLE role2 ( rid int(11) NOT NULL AUTO_INCREMENT,
2. role varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL
3. DEFAULT NULL, PRIMARY KEY (rid) USING BTREE ) ENGINE = InnoDB
4. AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci
5. ROW_FORMAT = Dynamic;

用户角色关系user2_role2表

1. CREATE TABLE user2_role2 ( id int(11) NOT NULL AUTO_INCREMENT,
2. uid int(11) NULL DEFAULT NULL, rid int(11) NULL DEFAULT NULL,
3. PRIMARY KEY (id) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 10
4. CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

初始化数据:

– 添加1个用户 INSERT INTO user2 VALUES (1, ‘user’, ‘user123’);

– 添加2个角色 INSERT INTO role2 VALUES (1, ‘user’); INSERT INTO role2 VALUES (2, ‘admin’);

– 1个用户,拥有2个角色 INSERT INTO user2_role2 VALUES (1, 1, 1); INSERT INTO user2_role2 VALUES (2, 1, 2);

使用Navicat可视化表的数据结构为:

创建entity实体类

User类

1. @Data
2. public class User {
3.     private Integer uid;
4.     private String username;
5.     private String password;
6. }


Role类

1. @Data
2. public class Role {
3.     private Integer rid;
4.     private String role;
5. }


创建DTO类

因为用户和角色是多对多关系,需要在用户中含有角色的对象,角色中含有用户的对象,创建DTO类而不再entity类中添加,是因为entity类属性是和表字段一一对应的,一般不推荐在entity类中添加与表字段无关的属性。

新建dto包,在包下创建如下类:

UserDTO类

// 注意,多对多不要用@Data,因为ToString会相互调用,导致死循环

1. @Setter
2. @Getter
3. public class UserDTO extends User {
4.     private Set<Role> roles;
5. }


RoleDTO类(目前用不到,可不建)

// 注意,多对多不要用@Data,因为ToString会相互调用,导致死循环

1. @Setter
2. @Getter
3. public class RoleDTO extends Role {
4.     private Set<User> users;
5. }


添加UserMapper.java类

1. @Mapper
2. @Repository
3. public interface UserMapper {
4.     // 查询用户
5.     UserDTO selectUserByUsername(@Param("username") String username);
6. }

UserMapper.xml。因为需要关联查询,所有使用xml方式

1. <?xml version="1.0" encoding="UTF-8" ?>
2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
3. <mapper namespace="com.example.bdatabasemanytomanyrole.mapper.UserMapper">
4. 
5.     <resultMap id="userRoleMap" type="com.example.bdatabasemanytomanyrole.dto.UserDTO">
6.         <id property="uid" column="uid"/>
7.         <result property="username" column="username"/>
8.         <result property="password" column="password"/>
9.         <collection property="roles" ofType="com.example.bdatabasemanytomanyrole.dto.RoleDTO">
10.             <id property="rid" column="rid"/>
11.             <result property="role" column="role"></result>
12.         </collection>
13.     </resultMap>
14.     <select id="selectUserByUsername" resultMap="userRoleMap">
15.         select user2.uid, user2.username, user2.password, role2.rid, role2.role
16.         from user2, user2_role2, role2
17.         where user2.username=#{username} 
18.         and user2.uid = user2_role2.uid 
19.         and user2_role2.rid = role2.rid
20.     </select>
21. </mapper>


UserService.java

1. @Service
2. public class UserService {
3.     @Autowired
4.     private UserMapper userMapper;
5. 
6.     public UserDTO getUser(String username){
7.         return userMapper.selectUserByUsername(username);
8.     }
9. }


UserController.java

1. @RestController
2. public class UserController {
3.     @Autowired
4.     private UserService userService;
5. 
6.     @GetMapping("/get-user")
7.     public UserDTO getUser(@RequestParam String username){
8.         return userService.getUser(username);
9.     }
10. }


上面完成后,启动项目,访问localhost:8080/get-user?username=user,查询到的用户信息为:

引入Spring Security安全验证

把pom.xml中的Spring Security依赖注释去掉:

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


此时,再重新启动程序,访问localhost:8080/get-user?username=user时,会跳转到登录页面。此时默认的登录用户名为user,密码在启动时打印在控制台。

从数据库中获取用户、密码进行登录:

添加MyUserDetailsService.java,实现UserDetailsService,重写loadUserByUsername方法:

1. @Component
2. public class MyUserDetailsService implements UserDetailsService {
3.     @Autowired
4.     private UserService userService;
5.     
6.     @Override
7.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
8.         UserDTO user = userService.getUser(username);
9.         if (user == null) {
10.             throw new UsernameNotFoundException("用户不存在");
11.         }
12.         // 添加用户拥有的多个角色
13.         List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
14.         Set<Role> roles = user.getRoles();
15.         for (Role role : roles) {
16.             grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRole()));
17.         }
18. 
19.         return new User(
20.                 user.getUsername(),
21.              // 数据库中密码没加密,需加密
22.                 new BCryptPasswordEncoder().encode(user.getPassword()),
23.                 grantedAuthorities
24.         );
25.     }
26. }


添加WebSecurityConfig.java,继承WebSecurityConfigurerAdapter,重写configure(AuthenticationManagerBuilder auth)方法:

1. @EnableWebSecurity
2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
3.     @Autowired
4.     private MyUserDetailsService userDetailsService;
5.     @Bean
6.     public PasswordEncoder passwordEncoder(){
7.         return new BCryptPasswordEncoder();
8.  }        
9.     @Override
10.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
11.         auth
12.                 .userDetailsService(userDatailService)
13.                 .passwordEncoder(passwordEncoder());
14.     }
15. }

重新启动,可以使用user/user123登录了。

查看登录用户信息

要查看登录用户信息,我们可以在UserController中添加方法:

1. @RestController
2. public class UserController {
3.     @Autowired
4.     private UserService userService;
5. 
6.     @GetMapping("/get-user")
7.     public UserDTO getUser(@RequestParam String username){
8.         return userService.getUser(username);
9.     }
10. 
11.     /**
12.      * 查看登录用户信息
13.      */
14.     @GetMapping("/get-auth")
15.     public Authentication getAuth(){
16.         return SecurityContextHolder.getContext().getAuthentication();
17.     }
18. }


重新启动登录后,访问localhost:8080/get-auth,返回

目录
相关文章
|
3月前
|
存储 安全 Java
SpringBoot搭建Spring Security 入门
SpringBoot搭建Spring Security 入门
114 0
|
2月前
|
存储 安全 Java
Spring Boot整合Spring Security--学习笔记
Spring Boot整合Spring Security--学习笔记
55 1
|
3月前
|
前端开发 安全 Java
SpringBoot 实现登录验证码(附集成SpringSecurity)
SpringBoot 实现登录验证码(附集成SpringSecurity)
|
22天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
|
6月前
|
安全 前端开发 Java
微服务技术系列教程(38)- SpringBoot -整合SpringSecurity
微服务技术系列教程(38)- SpringBoot -整合SpringSecurity
60 0
|
3月前
|
前端开发 JavaScript Java
基于Springboot+SpringSecurity+Activiti7实现的工作流系统可方便二次开发(附完整源码)
基于Springboot+SpringSecurity+Activiti7实现的工作流系统可方便二次开发(附完整源码)
59 0
|
4月前
|
安全 Java Spring
springboot整合spring security 实现用户登录注册与鉴权全记录
【1月更文挑战第11天】springboot整合spring security 实现用户登录注册与鉴权全记录
97 2
|
4月前
|
安全 Java Spring
springboot整合spring security 安全认证框架
springboot整合spring security 安全认证框架
|
5月前
|
安全 Java 数据安全/隐私保护
Spring Security在Spring Boot中的讲解与实战(附源码)
Spring Security在Spring Boot中的讲解与实战(附源码)
49 0
|
5月前
|
安全 Java 数据库
SpringBoot - 安全入门与SpringSecurity
SpringBoot - 安全入门与SpringSecurity
38 0