Spring 全家桶之 Spring Security(三)

2022-08-19 15:26:44 浏览数 (1)

一、自定义RBAC表实现认证

创建自定义的用户表,角色表和用户角色关系表

代码语言:javascript复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) NOT NULL,
  `rolememo` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `realname` varchar(255) DEFAULT NULL,
  `isenable` varchar(255) NOT NULL,
  `islock` varchar(255) NOT NULL,
  `iscredentials` varchar(255) DEFAULT NULL,
  `createtime` datetime DEFAULT NULL,
  `logintime` datetime DEFAULT NULL,
  `isexpire` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `userid` int(11) DEFAULT NULL,
  `roleid` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

创建Maven项目,加入依赖

代码语言:javascript复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  </dependencies>

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

配置application.properties,配置数据库连接信息

代码语言:javascript复制
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

自定义类SysUser实体类

自定义类SysUser类代替Spring Security中的UserDetails类,实现UserDetails中的方法, 放在entity包中,同时新增实体类SysRole

代码语言:javascript复制
public class SysUser implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private String realname;
    private boolean isExpired;
    private boolean isLocked;
    private boolean isCredentials;
    private boolean isEnabled;

    private Date createTime;
    private Date loginTime;

    private List<GrantedAuthority> authorities;


    public SysUser(){

    }

    public SysUser(String username, String password, String realname, boolean isExpired, boolean isLocked, boolean isCredentials, boolean isEnabled, Date createTime, Date loginTime,List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.realname = realname;
        this.isExpired = isExpired;
        this.isLocked = isLocked;
        this.isCredentials = isCredentials;
        this.isEnabled = isEnabled;
        this.createTime = createTime;
        this.loginTime = loginTime;
        this.authorities = authorities;
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return isExpired;
    }

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

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

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }
    // 此处省略getter/setter/toString()
}    
代码语言:javascript复制
public class SysRole {

    private Integer id;
    private String name;
    private String memo;
    
    //此处省略getter/setter/toString方法
}    

创建SysUserMapper接口

创建mapper包,新建SysUserMapper接口,新增insertSysUser(), selectByUser()方法

代码语言:javascript复制
@Repository
public interface SysUserMapper {

    int insertSysUser(SysUser user);
    //根据账号名称,获取用户信息
    SysUser selectSysUser(String username);
}

创建SysUserMapper.xml配置文件

在resource目录下新增mappers文件夹,新增SysUserMapper.xml配置文件

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.citi.mapper.SysUserMapper">

    <resultMap id="userMapper" type="com.citi.entity.SysUser">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password" />
        <result column="realname" property="realname" />
        <result column="isenable" property="isEnabled" />
        <result column="islock" property="isLocked" />
        <result column="iscredentials" property="isCredentials" />
        <result column="createtime" property="createTime" />
        <result column="logintime" property="loginTime" />
        <result column="isexpire" property="isExpired" />
    </resultMap>

    <insert id="insertSysUser">
        insert into sys_user(username,password,realname,
           isenable,islock,iscredentials,createtime,logintime)

         values(#{username},#{password},#{realname},#{isEnabled},
           #{isLocked},#{isCredentials},#{createTime},#{loginTime})
    </insert>


    <select id="selectSysUser" resultMap="userMapper">
        select id, username,password,realname,isexpire,
           isenable,islock,iscredentials,createtime,logintime
           from sys_user where username=#{username}
    </select>
</mapper>

配置MyBatis

在application.properties配置文件总增加mybatis配置

代码语言:javascript复制
# MyBatis设置
mybatis.mapper-locations=classpath:/mappers/*.xml
mybatis.type-aliases-package=com.citi.entity

创建启动类MainApplication

,新增jdbcInit()方法,在启动程序时初始化数据库,即往sys_user表里添加用户,创建三个用户Peter,Thor,Stark分别属于3个角色ADMIN,USER,READ,容器每次启动都会执行创建用户的操作,只在第一次启动时创建用户即可,创建完成之后可以将@PostConstruct注释即可

代码语言:javascript复制
@MapperScan("com.citi.mapper")
@SpringBootApplication
public class MainApplication {

    @Resource
    private SysUserMapper sysUserMapper;

    @PostConstruct
    public void jdbcInit(){
        List<GrantedAuthority> authorityList = new ArrayList<>();
        // 角色名称需要以ROLE_开头,后面加上自定义的角色名称
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"   "ADMIN");
        authorityList.add(authority);
        // 密码加密
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        SysUser user = new SysUser("Peter",passwordEncoder.encode("12345"),"Peter Parker",true,true,true,
                true, new Date(),new Date(),authorityList);
        sysUserMapper.insertSysUser(user);
    }

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

往sys_role,sys_user_role中添加数据

新增SysRoleMapper接口

在mapper包中新增SysRoleMapper接口,增加查询用户角色方法

代码语言:javascript复制
@Repository
public interface SysRoleMapper {

    List<SysRole> selectRoleByUser(Integer userId);
}

实现SysRoleMapper接口

在resources目录下的mappers文件夹中新增xml配置文件,实现Mapper接口中的查询方法

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.citi.mapper.SysRoleMapper">

    <resultMap id="roleMapper" type="com.citi.entity.SysRole">
        <id column="id" property="id" />
        <result column="rolename" property="name" />
        <result column="rolememo" property="demo" />
    </resultMap>

    <select id="selectRoleByUser" parameterType="integer" resultMap="roleMapper">
        SELECT r.id, r.rolename, rolememo
        FROM sys_user_role ur, sys_role r
        WHERE ur.roleid = r.id
        AND ur.userid = #{userId}
    </select>
</mapper>

创建自定义的UserDetailsService实现类

重写方法中查询数据库获取用户信息,获取角色数据,构建UserDetails实现类对象

代码语言:javascript复制
@Service
public class JdbcUserDetailsService implements UserDetailsService {

    @Resource
    private SysUserMapper userMapper;

    @Resource
    private SysRoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.根据username获取SysUser
        SysUser user = userMapper.selectSysUser(username);
        // 2.根据userId获取用户角色
        if (user != null){
            List<SysRole> sysRoles = roleMapper.selectRoleByUser(user.getId());
            String roleName = "";
            List<GrantedAuthority> authorities = new ArrayList<>();

            for (SysRole sysRole : sysRoles) {
                roleName = sysRole.getName();
                SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"   roleName);
                authorities.add(grantedAuthority);
            }

            return user;
        }

        return user;
    }
}

新建CustSecurityConfig配置列

在config包下新建CustSecurityConfig,自定义WebSecurityConfigurerAdapter,自定义安全配置

代码语言:javascript复制
@Configuration
@EnableWebSecurity
public class CustSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

新增UserController

新增controller包,新增UserController及index.html页面

代码语言:javascript复制
@RestController
public class UserController {

    @GetMapping(value = "/access/user", produces = "text/html;charset=utf-8")
    public String user(){
        return "USER";
    }

    @GetMapping(value = "/access/read", produces = "text/html;charset=utf-8")
    public String read(){
        return "READ";
    }

    @GetMapping(value = "/access/admin", produces = "text/html;charset=utf-8")
    public String admin(){
        return "ADMIN";
    }
}
代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<p>验证访问</p>
<a href="/access/user">USER</a>
<a href="/access/read">READ</a>
<a href="/access/admin">ADMIN</a>
</body>
</html>

启动MainApplication

启动MainApplication,打开localhost:8080,自动跳转至localhost:8080/login

输入用户名密码Thor/12345,Peter/12345,Stark/12345,可以成功跳转至index页面

给index页面去除权限控制,在CustSecurityConfig类中新增重载的configure方法,出了index页面可以直接访问,其他页面都需要权限验证

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests().antMatchers("/index.html").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin();
}

重启启动应用,清楚浏览器缓存,在浏览器中输入localhost:8080,页面直接显示,不需要在进行登录操作,随意点击index页面的一个连接跳出认证页面

16.给URL配置角色访问权限,修改configure(HttpSecurity http)方法

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests().antMatchers("/index.html").permitAll()
            // 给url配置角色访问权限
            .antMatchers("/access/user").hasRole("USER")
            .antMatchers("/access/read").hasRole("READ")
            .antMatchers("/access/admin").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin();
}

以上代码给USER角色配置了/access/user的访问权限,给READ角色配置了/access/read的访问权限 ,给ADMIN角色配置了/access/admin的访问权限

17.验证权限,重新启动应用,打开首页如下,为保证正确性,首先清楚浏览器缓存,三个用户Peter,Thor,Stark分别属于3个角色ADMIN,USER,READ,Thor账户还拥有ADMIN的权限

首先验证USER角色,点击USER链接,输入Thor/123456,正常访问

其他页面,访问异常

由于Thor账户还有ADMIN角色,所以ADMIN页面也是可以正常访问的

清楚浏览器缓存,使用READ角色的Stark账号访问,read页面可以正常访问

试试其他页面,访问user页面和admin页面均报错

当然也可以给一个角色配置多个页面访问权限

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests().antMatchers("/index.html").permitAll()
            // 给url配置角色访问权限
            .antMatchers("/access/user","/access/read").hasRole("USER")
            .antMatchers("/access/read").hasRole("READ")
            .antMatchers("/access/admin","/access/read","/access/user").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin();
}

antMatchers()参数为String...antPatterns,不限制url的个数。

目前为止,已经实现在自定义数据库表的情况下实现用户认证和权限的鉴别,并且通过了测试。

0 人点赞