3.SpringSecurity database-based authentication and authorization

SpringSecurity database-based authentication and authorization

Undertaken by:2.SpringSecurity - A brief description of the processor-CSDN Blog

The user information we learned before is configured in the code, as shown in the following code snippet

/**
 * 定义一个Bean,用户详情服务接口
 * <p>
 * 系统中默认是有这个UserDetailsService的,也就是默认的用户名(user)和默认密码(控制台生成的)
 * 如果在yaml文件中配置了用户名和密码,那在系统中的就是yaml文件中的信息
 * <p>
 * 我们自定义了之后,就会把系统中的UserDetailsService覆盖掉
 */
@Configuration
public class MySecurityUserConfig {
    
    
    /**
     * 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetails(包括用户名、密码、用户所拥有的权限)
     * <p>
     * UserDetails存储的是用户的用户名、密码、去权限信息
     */
    @Bean
    public UserDetailsService userDetailsService() {
    
    
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainer
        UserDetails user1 = User.builder()
                .username("zhangjingqi-1")
                .password(passwordEncoder().encode("123456"))
//               配置用户角色
                .roles("student") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager
                .build();

        UserDetails user2 = User.builder()
                .username("zhangjingqi-2")
                .password(passwordEncoder().encode("123456"))
//              配置权限
                .authorities("teacher:query")
                .build();

        UserDetails user3 = User.builder()
                .username("admin")
                .password(passwordEncoder().encode("123456"))
//              配置权限
                .authorities("teacher:query","teacher:add","teacher:update","teacher:delete")
                .build();

//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsService
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(user1);
        userDetailsManager.createUser(user2);
        userDetailsManager.createUser(user3);
        return userDetailsManager;
    }

    /**
     * 配置密码加密器
     * NoOpPasswordEncoder.getInstance() 此实例表示不加密
     * BCryptPasswordEncoder() 会加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

}

But we don’t want to write it like this. We want to store the user’s information in the database.

1. Customized user information UserDetails

The user entity class needs to implement the UserDetails interface and implement the 7 methods in this interface. The 7 methods of the UserDetails interface are as follows:

img

1.1 Create a new user information class UserDetails

//只有当"accountNonExpired"、“accountNonLocked”、“credentialsNonExpired”、"enabled"都为true时,账户才能使用
//之前我们创建的时候,直接User.builder()创建,之后InMemoryUserDetailsManager对象createUser
public class SecurityUser implements UserDetails {
    
    

    /**
     * @return 权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        return null;
    }

    /**
     * @return 用户密码,一定是加密后的密码
     */
    @Override
    public String getPassword() {
    
    
        //明文为123456
        return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";
    }

    /**
     * @return 用户名
     */
    @Override
    public String getUsername() {
    
    
        return "thomas";
    }

    /**
     * @return 账户是否过期
     */
    @Override
    public boolean isAccountNonExpired() {
    
    
        return true;
    }

    /**
     * @return 账户是否被锁住
     */
    @Override
    public boolean isAccountNonLocked() {
    
    
        return true;
    }

    /**
     * @return 凭据是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return true;
    }

    /**
     * @return 账户是否可用
     */
    @Override
    public boolean isEnabled() {
    
    
        return true;
    }
}

1.2 UserDetailsService

/**
 * 当我们定义了此类后,系统默认的UserDetailsService不会起作用,下面UserServiceImpl会起作用
 */
@Service
public class UserServiceImpl implements UserDetailsService {
    
    

    /**
     * 根据用户名获取用户详情UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        SecurityUser securityUser= new SecurityUser();//我们自己定义的
        if(username==null || !username.equals(securityUser.getUsername())){
    
    
//          SpringSecurity框架中自带的异常
            throw new UsernameNotFoundException("该用户不存在或用户名不正确");
        }
//      执行到这里,说明username是没有问题的
//      用户密码对不对,框架会帮我们进行判断
        return securityUser;
    }

}

2. Database-based authentication

We observed that in 1.1UserDetails, we hard-coded the username and password, but this is unreasonable in this case. We need to take them out from the database, including permissions.

After fetching the information from the database, the information can be encapsulated into a UserDetails class

image-20231019221142173

2.1 Connect to database

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
spring:
  #数据源
  datasource:
    #德鲁伊连接池
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/springsecurity?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: root
      
 mybatis:
  #SQL映射文件的位置
  mapper-locations: classpath:mapper/**/*.xml
  # 指定实体类起别名,(实体类所在的包的包路径,那么包中的所有实体类别名就默认是类名首字母小写)
  type-aliases-package: com.zhangjingqi.entity
  configuration:
    #开启驼峰命名法
    map-underscore-to-camel-case: true
    #日志功能
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl     

2.2 Obtain user information

To determine whether the user is correct, we can take out the user-related information from sys_user, take out the user-related information and encapsulate it into a UserDetails class

image-20231020091944626

image-20231020101927297

2.2.1 Obtain user entity class

Entity class for obtaining user information. It is not recommended that the SysUser entity class implements the UserDetails interface because it will look messy

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysUser implements Serializable {
    
    

    private static final long serialVersionUID = -5352627792860514242L;
    
    private Integer userId;

    private String username;

    private String password;

    private String sex;

    private String address;

    private Integer enabled;

    private Integer accountNoExpired;

    private Integer credentialsNoExpired;

    private Integer accountNoLocked;

}

2.2.2 Mapper

Encapsulation dao layer, which is the Mapper layer, obtains user information from the database

@Mapper
public interface SysUserDao {
    
    

    /**
     * 根据用户名访问用户信息
     * @param userName 用户名
     * @return 用户信息
     */
    SysUser getByUserName(@Param("userName") String userName);


}
<mapper namespace="com.zhangjingqi.dto.SysUserDao">

    <select id="getByUserName" resultType="com.zhangjingqi.entity.SysUser">
         select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_locked
        from sys_user 
        where username = #{userName};
    </select>
    
</mapper>

2.2.3 Service

@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {
    
    
    
    @Autowired
    private SysUserDao sysUserDao;

    @Override
    public SysUser getByUserName(String userName) {
    
    
        return sysUserDao.getByUserName(userName);
    }
}

2.3 Certification

2.3.1 Implement UserDetails interface

We used to hard-code user information, but now we get it from the database.

@Data
public class SecurityUser implements UserDetails {
    
    

    private static final long serialVersionUID = -1314948905954698478L;

    private final SysUser sysUser ;


    public SecurityUser(SysUser sysUser) {
    
    
        this.sysUser = sysUser;
    }


    /**
     * @return 权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        return null;
    }

    /**
     * @return 用户密码,一定是加密后的密码
     */
    @Override
    public String getPassword() {
    
    
        return sysUser.getPassword();
    }

    /**
     * @return 用户名
     */
    @Override
    public String getUsername() {
    
    
        return sysUser.getUsername();
    }

    /**
     * @return 账户是否过期
     */
    @Override
    public boolean isAccountNonExpired() {
    
    
        return sysUser.getAccountNoExpired() != 0;
    }

    /**
     * @return 账户是否被锁住
     */
    @Override
    public boolean isAccountNonLocked() {
    
    
        return sysUser.getAccountNoLocked() !=0;
    }

    /**
     * @return 凭据是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return sysUser.getCredentialsNoExpired() !=0 ;
    }

    /**
     * @return 账户是否可用
     */
    @Override
    public boolean isEnabled() {
    
    
        return sysUser.getEnabled() !=0 ;
    }
}

This is what we wrote about this place before

image-20231020110240997

2.3.2 Implement the UserDetailsService interface

@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {
    
    

    @Autowired
    private SysUserService sysUserService;

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

//      1.从数据库获取用户的详情信息
        SysUser sysUser = sysUserService.getByUserName(username);

        if (null == sysUser){
    
    
//          这个异常信息是SpringSecurity中封装的
            throw new UsernameNotFoundException("用户没有找到");
        }

//      2.封装成UserDetails类,SecurityUser类实现了UserDetails接口
        SecurityUser securityUser = new SecurityUser(sysUser);

        return securityUser;
    }
}

This is what we wrote before

This article will introduce:1. SpringSecurity - Quick Start, Encryption, Basic Authorization-CSDN Blog

image-20231020105535812

2.3.3 Security configuration class

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    /**
     * 重写 configure(HttpSecurity http)方法
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    

        http.authorizeRequests()//授权http请求
                .anyRequest()//任何请求
                .authenticated();//需要验证

        http.formLogin().permitAll(); //SpringSecurity的表单认证
    }


    /**
     * @return 密码加密器
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
    
    
        return new BCryptPasswordEncoder();
    }

}

2.3.4 Test procedures

2.3.4.1 Controller
@RestController
@RequestMapping("/student")
public class StudentController {
    
    

    @GetMapping("/query")
    private String query(){
    
    
        return "query student";
    }

    @GetMapping("/add")
    private String add(){
    
    
        return "add student";
    }

    @GetMapping("/delete")
    private String delete(){
    
    
        return "delete student";
    }

    @GetMapping("/update")
    private String update(){
    
    
        return "export student";
    }
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {
    
    

    @GetMapping("/query")
    @PreAuthorize("hasAuthority('teacher:query')")//预授权
    public String queryInfo() {
    
    
        return "teacher query";
    }

}
3.3.4.1 Access

Log in with the following person’s information

image-20231020140635419

Visit itlocalhost:8080/student/query and find that it can be accessed

image-20231020142807340

Visit againlocalhost:8080/teacher/query and find that it cannot be accessed because the thomas user does not have a /teacher/query path Permissions

image-20231020142907797

3. Database-based authorization

3.1 Database table

First of all, we are very clear that the relationship between users and roles is many-to-many.

user table

image-20231020150336382

image-20231020150438581

character sheet

image-20231020150353283

image-20231020150456440

User and role association table

image-20231020150418327

Permissions table

image-20231021145545952

image-20231021145604961

Permission and role association table

image-20231021145635383

image-20231021145648250

3.2 Obtaining permissions

3.2.1 Permission class SysMenu

@Data
public class SysMenu implements Serializable {
    
    
    
    private static final long serialVersionUID = 597868207552115176L;
    
    private Integer id;
    private Integer pid;
    private Integer type;
    private String name;
    private String code;
}

3.2.2 SysMenuDao

@Mapper
public interface SysMenuDao {
    
    
   List<String> queryPermissionByUserId(@Param("userId") Integer userId);
}

This place involvesthree tables, role-user association table sys_role_user, role-permission association table sys_role_menu, and authority table sys_menu

We need to obtain the corresponding permissions through the user

<mapper namespace="com.zhangjingqi.dto.SysMenuDao">


    <select id="queryPermissionByUserId" resultType="java.lang.String">
        SELECT distinct sm.code
        FROM sys_role_user sru
                 inner join sys_role_menu srm
                            on sru.rid = srm.rid
                 inner join sys_menu sm on srm.mid = sm.id
        where sru.uid = #{userId}
          and sm.delete_flag = 0

    </select>
</mapper>

3.2.3 SysMenuServiceImpl

@Service
public class SysMenuServiceImpl implements SysMenuService {
    
    
    @Autowired
    private SysMenuDao sysMenuDao;

    @Override
    public List<String> queryPermissionByUserId(Integer userId) {
    
    
        return sysMenuDao.queryPermissionByUserId(userId);
    }
}

3.2.4 Modify UserDetailsService

@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {
    
    

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysMenuService sysMenuService;

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

//      1.从数据库获取用户的详情信息
        SysUser sysUser = sysUserService.getByUserName(username);

        if (null == sysUser){
    
    
//          这个异常信息是SpringSecurity中封装的
            throw new UsernameNotFoundException("用户没有找到");
        }

//      2.获取该用户的权限
        List<String> permissionList = sysMenuService.queryPermissionByUserId(sysUser.getUserId());

//        List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(permission -> new SimpleGrantedAuthority(permission) ).collect(Collectors.toList());
//      将集合的泛型转换成SimpleGrantedAuthority (GrantedAuthority类的子类即可)
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());


//      2.封装成UserDetails类,SecurityUser类实现了UserDetails接口
        SecurityUser securityUser = new SecurityUser(sysUser);
        securityUser.setSimpleGrantedAuthorities(simpleGrantedAuthorities);

        return securityUser;
    }
}

3.2.5 Modify UserDetails

@Data
public class SecurityUser implements UserDetails {
    
    

    private static final long serialVersionUID = -1314948905954698478L;

    private final SysUser sysUser ;

//  用户权限
    private List<SimpleGrantedAuthority> simpleGrantedAuthorities;

    public SecurityUser(SysUser sysUser) {
    
    
        this.sysUser = sysUser;
    }


    /**
     *  这个集合中对象的类型必须是GrantedAuthority类或其子类
     * @return 权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        return simpleGrantedAuthorities;
    }

    /**
     * @return 用户密码,一定是加密后的密码
     */
    @Override
    public String getPassword() {
    
    
        return sysUser.getPassword();
    }

    /**
     * @return 用户名
     */
    @Override
    public String getUsername() {
    
    
        return sysUser.getUsername();
    }

    /**
     * @return 账户是否过期
     */
    @Override
    public boolean isAccountNonExpired() {
    
    
        return sysUser.getAccountNoExpired() != 0;
    }

    /**
     * @return 账户是否被锁住
     */
    @Override
    public boolean isAccountNonLocked() {
    
    
        return sysUser.getAccountNoLocked() !=0;
    }

    /**
     * @return 凭据是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return sysUser.getCredentialsNoExpired() !=0 ;
    }

    /**
     * @return 账户是否可用
     */
    @Override
    public boolean isEnabled() {
    
    
        return sysUser.getEnabled() !=0 ;
    }
}

3.3 Testing

Login with Obama

image-20231021155010195

View obama permissions

image-20231021155118766

4. Summary

Authentication and authorization require us to implement the UserDetails user details class and UserDetailsService class

Guess you like

Origin blog.csdn.net/weixin_51351637/article/details/134063693