你的浏览器不支持canvas

做你害怕做的事情,然后你会发现,不过如此。

Spring Boot+Spring Security+Mybatis实现登录和角色访问权限

时间: 作者: 黄运鑫

本文章属原创文章,未经作者许可,禁止转载,复制,下载,以及用作商业用途。原作者保留所有解释权。


1.创建Spring Boot+Mybatis的maven项目

  • 在IDEA中点击File——New——Project新建项目,选择好jdk后点击next:

image

  • 在此页面可以修改项目的信息,默认信息如下:

image

  • 点击next到勾选依赖页面,勾选webmysqljdbcmybatis如下:

image

image

  • 点击next后填写项目名称,点击Finish创建完成,等待maven下载依赖包完成,项目结构如下:

image

  • 可以看到maven的pom.xml文件中已经添加了刚才勾选的webmysqljdbcmybatis依赖,内容如下:

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.1.3.RELEASE</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>com.example</groupId>
          <artifactId>demo</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <name>demo</name>
          <description>Demo project for Spring Boot</description>
        
          <properties>
              <java.version>1.8</java.version>
          </properties>
        
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-jdbc</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.mybatis.spring.boot</groupId>
                  <artifactId>mybatis-spring-boot-starter</artifactId>
                  <version>2.0.0</version>
              </dependency>
        
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <scope>runtime</scope>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
              </dependency>
          </dependencies>
        
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
        
      </project>
    
  • 创建数据库,例子中数据库名称为demo

image

  • resources下的application.properties中配置数据库连接参数:
    #数据库连接
    spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=你的密码
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
  • 增加IndexControllerindex.html页面:

image

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
访问成功
</body>
</html>
  • 注意:如果你的controller和启动类Application不在同一包下,则需要@ComponentScan注解来指定需要加载的bean路径。

  • 因为页面放到了templates下,所以需要在pom.xml文件中加入thymeleaf依赖包:

      <!--thymeleaf依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
    
  • 到此项目就创建完成。启动项目,访问localhost:8080,成功页面如下:

image

2.Spring Security配置

2.1 使用Spring Security原生的页面登录

  • pom.xml文件中加入Spring Security依赖包:

      <!--security依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
    
  • 此时Spring Security已经生效。如果重新启动项目,访问localhost:8080,会重定向到localhost:8080/login页面,这是Spring Security自带的登录页面,如下:

image

  • 默认的用户名为user,密码会在启动项目时输出到控制台:

image

  • 输入用户名和控制台打印的密码,登录成功即可跳转到首页。

2.2 通过配置,使用自定义的页面登录

  • 增加LoginControllerlogin.html页面:

image

@Controller
public class LoginController {
    
    @GetMapping("/login")
    public String login(){
        return "login";
    }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>登录</title>
</head>
<body>
<div id="login-box">
    <form th:action="@{/login}" method="post">
        帐号:<input type="text" name="username">
        <br/>
        密码:<input type="password" name="password">
        <br/>
        <button type="submit">登录</button>
        <span th:if="${param.error}">登录失败</span>
        <span th:if="${param.logout}">退出登录成功</span>
    </form>
</div>
</body>
</html>
  • 修改index.html页面,加入退出登录按钮:

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>首页</title>
      </head>
      <body>
      访问成功
      <a th:href="@{/logout}">点击退出登录</a>
      </body>
      </html>
    
  • 添加CustomUserService并实现UserDetailsService,用于根据表单提交的用户名加载对应的用户信息(后续改为从数据库加载用户):

image

public class CustomUserService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) {
        //添加用户的角色,用于权限验证
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        
        //创建用户:用户名user,密码123,角色ADMIN
        return new User("user", this.passwordEncoder.encode("123"), authorities);
    }
}
  • 增加WebSecurityConfig配置类,继承WebSecurityConfigurerAdapter来配置访问规则:

image

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

    //将CustomUserService注册为bean
    @Bean
    public CustomUserService customUserService() {
        return new CustomUserService();
    }

    //使用自定义的CustomUserService加载用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.customUserService());
        super.configure(auth);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login")//设置登录页面
                .loginProcessingUrl("/login")//自定义的登录接口
                .failureUrl("/login?error")//登录失败的页面
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()//设置不需要登陆就能访问登录页面
                .anyRequest()//登录后可以访问所有页面
                .authenticated()
                .and()
                .csrf().disable();//关闭csrf防护
    }
}
  • 到此使用自定义页面登录就配置成功了,重启项目,此时localhost:8080/login显示的是自定义的登录页面:

image

  • 输入用户名user,密码123即可登录成功:

image

2.3 配置Mybatis动态查询数据库的用户和角色

  • 数据库中创建表sys_usersys_rolesys_user_role,用来记录用户、角色、用户和角色的关系:

      CREATE TABLE `sys_user` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `user_name` varchar(16) DEFAULT NULL,
        `password` varchar(128) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
        
      CREATE TABLE `sys_role` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `name` varchar(16) DEFAULT NULL COMMENT '角色名称',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
        
      CREATE TABLE `sys_user_role` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `user_id` bigint(20) DEFAULT NULL,
        `role_id` bigint(20) DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
    
  • 手动在表中添加数据:

  • sys_user表,注意:密码是用new BCryptPasswordEncoder().encode("123")加密后的数据。

image

  • sys_role表。

image

  • sys_user_role表。

image

  • resources下的application.properties中开启驼峰映射:

      #数据库连接
      spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
      spring.datasource.username=root
      spring.datasource.password=你的密码
      spring.datasource.driver-class-name=com.mysql.jdbc.Driver
      #驼峰映射
      mybatis.configuration.map-underscore-to-camel-case=true 
    
  • 驼峰映射就是将数据库中的xxx_yyy字段映射为实体类中的xxxYyy属性,比如:字段user_name映射的是实体类中的userName属性。实现Mybatis的映射有四种方式,具体可以参考另一篇文章Mybatis数据库字段和实体属性的映射

  • 创建Mybatis的实体类、mapper和service,用来查询数据库:

image

  • 创建实体:

      public class SysUser {
          private Long id;
          private String userName;
          private String password;
          private List<SysRole> sysRoleList;
        
          public Long getId() {
              return id;
          }
        
          public void setId(Long id) {
              this.id = id;
          }
        
          public String getUserName() {
              return userName;
          }
        
          public void setUserName(String userName) {
              this.userName = userName;
          }
        
          public String getPassword() {
              return password;
          }
        
          public void setPassword(String password) {
              this.password = password;
          }
        
          public List<SysRole> getSysRoleList() {
              return sysRoleList;
          }
        
          public void setSysRoleList(List<SysRole> sysRoleList) {
              this.sysRoleList = sysRoleList;
          }
      }
    
      public class SysRole {
          private Long id;
          private String name;
        
          public Long getId() {
              return id;
          }
        
          public void setId(Long id) {
              this.id = id;
          }
        
          public String getName() {
              return name;
          }
        
          public void setName(String name) {
              this.name = name;
          }
      }
    
      public class SysUserRole {
          private Long id;
          private Long userId;
          private Long roleId;
            
          public Long getId() {
              return id;
          }
        
          public void setId(Long id) {
              this.id = id;
          }
        
          public Long getUserId() {
              return userId;
          }
        
          public void setUserId(Long userId) {
              this.userId = userId;
          }
        
          public Long getRoleId() {
              return roleId;
          }
        
          public void setRoleId(Long roleId) {
              this.roleId = roleId;
          }
      }
    
  • 创建Mapper用来执行查询语句:
  • 因为为了省事使用了驼峰映射,所以不需要使用xml文件来配置字段和实体的映射关系,直接使用注解编写sql即可。

      @Mapper
      @Repository
      public interface SysUserMapper {
        
          @Select("SELECT * FROM sys_user")
          List<SysUser> findAll();
        
          @Select("SELECT * FROM sys_user WHERE user_name = #{userName}")
          SysUser findByUserName(@Param("userName") String userName);
      }
    
      @Mapper
      @Repository
      public interface SysRoleMapper {
        
          @Select("SELECT * FROM sys_role WHERE id = #{id}")
          SysRole findById(@Param("id") Long id);
      }
    
      @Mapper
      @Repository
      public interface SysUserRoleMapper {
        
          @Select("SELECT * FROM sys_user_role WHERE user_id = #{userId}")
          List<SysUserRole> findByUserId(@Param("userId") Long userId);
      }
    
  • 创建service用来编写业务代码:

      @Service
      public class SysUserService {
          @Autowired
          private SysUserMapper sysUserMapper;
        
          public List<SysUser> findAll() {
              return sysUserMapper.findAll();
          }
        
          public SysUser findByUserName(String userName) {
              return sysUserMapper.findByUserName(userName);
          }
      }
    
      @Service
      public class SysRoleService {
          @Autowired
          private SysRoleMapper sysRoleMapper;
            
          public SysRole findById(Long id) {
              return sysRoleMapper.findById(id);
          }
      }
    
      @Service
      public class SysUserRoleService {
          @Autowired
          private SysUserRoleMapper sysUserRoleMapper;
        
          public List<SysUserRole> findByUserId(Long userId) {
              return sysUserRoleMapper.findByUserId(userId);
          }
      }
    
  • 修改WebSecurityConfig配置类,设置首页需要ADMIN角色才能访问:

      @Configuration
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        
          //用于密码加密
          @Bean
          public PasswordEncoder passwordEncoder() {
              return new BCryptPasswordEncoder();
          }
        
          //将CustomUserService注册为bean
          @Bean
          public CustomUserService customUserService() {
              return new CustomUserService();
          }
        
          //使用自定义的CustomUserService加载用户
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(this.customUserService());
              super.configure(auth);
          }
        
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin()
                      .loginPage("/login")//设置登录页面
                      .loginProcessingUrl("/login")//自定义的登录接口
                      .failureUrl("/login?error")//登录失败的页面
                      .and()
                      .authorizeRequests()
                      .antMatchers("/login").permitAll()//设置不需要登陆就能访问登录页面
                      .antMatchers("/").hasAnyRole("ADMIN")//设置首页需要ADMIN角色才能访问
                      .anyRequest()//登录后可以访问所有页面
                      .authenticated()
                      .and()
                      .csrf().disable();//关闭csrf防护
          }
      }
    
  • 修改CustomUserService配置类,改为从数据库获取用户和角色:
  • 需要注意,数据库保存的密码必须是用new BCryptPasswordEncoder().encode("密码")加密后的数据,否则不能登录。

      public class CustomUserService implements UserDetailsService {
        
          @Autowired
          private SysUserMapper userMapper;
          @Autowired
          private SysUserRoleMapper sysUserRoleMapper;
          @Autowired
          private SysRoleMapper sysRoleMapper;
        
          @Override
          public UserDetails loadUserByUsername(String username) {
        
              //通过用户名查询用户
              SysUser user = userMapper.findByUserName(username);
              if (user == null) {
                  throw new UsernameNotFoundException("用户名不存在");
              }
        
              //查询用户的角色信息
              List<SimpleGrantedAuthority> authorities = new ArrayList<>();
              List<SysUserRole> sysUserRoleList = sysUserRoleMapper.findByUserId(user.getId());
              for (SysUserRole sysUserRole : sysUserRoleList) {
                  SysRole sysRole = sysRoleMapper.findById(sysUserRole.getRoleId());
                  if (sysRole != null) {
                      authorities.add(new SimpleGrantedAuthority(sysRole.getName()));
                  }
              }
        
              //返回查询到的用户,验证能否登录
              return new User(user.getUserName(), user.getPassword(), authorities);
          }
      }
    
  • 重新启动项目,访问localhost:8080,此时会跳转到自定义登录页面,输入刚才添加到数据库中的用户名和密码(密码是加密前的明文密码,例子中的用户名:user 密码:123),点击登录,页面报错如下:

image

  • 报错信息为403表示服务器拒绝访问,说明我们配置的角色起作用了。拒绝访问是因为数据库中的角色是USER,而首页需要ADMIN角色才允许访问。
  • 我们将数据库表sys_role中的角色ROLE_USER改为ROLE_ADMIN,重新登录:

image

  • 登录成功了:

image


对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。