这是Springboot与Shiro整合系列文章的第二节,本节的目标能够使用Shiro完成用户名密码的校验。

为了更加贴合实际的项目,会将用户信息存入到数据库表中

本次我们将按照以下步骤进行整合:

  1. 创建数据库表存储用户信息
  2. 引入mybatis并且生成Mapper来操作数据库表
  3. 创建自定义的Realm来获取认证信息
  4. 创建登录service来测试我们的代码

01

数据库表的创建

在这里我们只创建最基本的,必须的字段,如果实际业务上还有其他的需求,可以自己扩展该表的字段。

CREATE TABLE `sys_user` ( `userid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id', `username` varchar(255) NOT NULL COMMENT '用户名', `passowrd` varchar(255) NOT NULL COMMENT '密码', `locked` bit(1) DEFAULT b'0' COMMENT '是否锁定', `real_name` varchar(255) DEFAULT NULL COMMENT '真实姓名', PRIMARY KEY (`userid`), UNIQUE KEY `username_unique_index` (`username`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;

02

Mybatis引入

在Pom.xml文件中引入以下依赖

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

下面我们来根据数据库表来生成相应的Mapper, 这里强烈建议使用 MyBatisCodeHelperPro 插件,因为它是我用过的最好的Mybatis插件,功能十分强大。

1. 在IDEA建立数据库的连接

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(1)

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(2)

2. 使用MyBatisCodeHelperPro 插件创建Mapper

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(3)

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(4)

3. 检查插件是否帮我们生成成功相关代码

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(5)

4. 在application.properties 文件中配置工程连接的数据库信息以及mybatis mapper文件的位置

spring.datasource.username=root spring.datasource.password=****自己的密码***** spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC spring.datasource.driver-class-name=com.mysql.jdbc.Driver mybatis.mapper-locations=classpath:/mapperxml/*.xml

03

创建Realm来验证用户名密码

1.创建UserNameToken, 此类用来承载我们的用户名和密码来和Shiro的Subject来进行交互,

在这里有一个重要的概念一定要记住: token中的principal 和 credentials代表的是什么含义,那么在其他类中principal 和credentials也要代表同样的含义,否则会引起认证缓存在logout的时候无法删除的问题。

在此UserNameToken 中 principal就是用户名, credentials就是密码,所以后边的UserNamePasswordRealm中principal 也是用户名crdentials也是面命

package com.example.shirospringboot.config; import org.apache.shiro.authc.AuthenticationToken; //此token一定要实现AuthenticationToken public class UserNamePasswordToken implements AuthenticationToken { private String userName; private String password; 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; } @Override public Object getPrincipal() { return userName; } @Override public Object getCredentials() { return password; } }

2. 创建UserNamePasswordRealm,此Realm只支持上边创建的UserNamePasswordToken,获取用户信息以及除密码校验的信息校验逻辑都在Realm中实现

我们先看一下Realm中的大体逻辑

Realm逻辑

  1. 通过用户名从数据库中查询出来用户的信息
  2. 如果用户的信息为空或者用户被锁定或者其他用户的异常,直接抛出认证异常
  3. 将查询出来的用户信息,封装成为指定的类型(此处不需要比较数据库中的密码和传入的密码是否相同,Shiro的CredentialsMatcher会帮我们自动比对),

//删除掉它,否则会影响后边的结果 //因为默认的多Realm执行策略的问题,这个在后续文章中还会涉及到 @Bean public Realm realm() { TextConfigurationRealm realm = new TextConfigurationRealm(); realm.setUserDefinitions("joe.coder=password,user\n" "jill.coder=password,admin"); realm.setRoleDefinitions("admin=read,write\n" "user=read"); realm.setCachingEnabled(true); return realm; }

获取用户信息我们需要新增以下Mapper代码

SysUserMapper.java增加如下代码

SysUserselectAllByUsername(@Param("username")Stringusername)

SysUserMapper.xml 增加如下代码

<select id="selectAllByUsername" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from sys_user where username=#{username} </select>

UserNamePasswordRealm.java 代码如下

package com.example.shirospringboot.config; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.util.ByteSource; import org.springframework.stereotype.Component; import com.example.shirospringboot.mapper.SysUserMapper; import com.example.shirospringboot.model.SysUser; /** * 此realm只是用做认证使用,所以继承了AuthenticatingRealm */ @Component public class UserNamePaswordRealm extends AuthenticatingRealm { @Autowired private SysUserMapper sysUserMapper; //表明此Realm只是支持我们创建的UserNamePasswordRealm @Override public boolean supports(AuthenticationToken token) { return token instanceof UserNamePasswordToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String)token.getPrincipal(); SysUser sysUser = sysUserMapper.getAllByUsername(userName); if (sysUser == null) { throw new UnknownAccountException("用户名或密码错误"); } if(sysUser.getLocked() != null && sysUser.getLocked()){ throw new LockedAccountException("该用户已经被锁定"); } SimpleAuthenticationInfosimpleAuthenticationInfo=newSimpleAuthenticationInfo(userName,sysUser.getPassowrd(),sysUser.getRealName()); return simpleAuthenticationInfo ; } }

3. 手工在数据库中添加一条记录

INSERTINTO`sys_user`VALUES('23','admin','123','\0','管理员');

请记住用户名是admin 密码是123,后边的测试我们会用到

04

创建测试service

1.创建UserNamePasswordBean类用于接收前端传来的用户名和密码

package com.example.shirospringboot.requestBean; import javax.validation.constraints.NotBlank; public class UserNamePasswordBean { @NotBlank(message = "密码不为空") private String password; @NotBlank(message="用户名不为空") private String username; 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; } }

上述代码中的校验注解生效需要引入下面的依赖

<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>7.0.1.Final</version> </dependency>

2.创建LoginController 用来进行登录的测试

package com.example.shirospringboot.controller; import org.apache.shiro.SecurityUtils; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import com.example.shirospringboot.config.UserNamePasswordToken; import com.example.shirospringboot.requestBean.UserNamePasswordBean; @Controller public class LoginController { public ResponseEntity login(@RequestBody UserNamePasswordBean userNamePasswordBean) { UserNamePasswordToken userNamePasswordToken = new UserNamePasswordToken(); userNamePasswordToken.setUserName(userNamePasswordBean.getUsername()); userNamePasswordToken.setPassword(userNamePasswordBean.getPassword()); SecurityUtils.getSubject().login(userNamePasswordToken); return ResponseEntity.ok().build(); } }

同时创建全局异常处理器,当认证异常的时候可以进行异常的捕获

package com.example.shirospringboot.controller; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice public class AppExceptionHandler { /** * 用来处理密码不正确 * @return */ @ExceptionHandler(IncorrectCredentialsException.class) public ResponseEntity incorrectCredentialsException() { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码不正确"); } /** * 用来处理其他的认证异常 * @param e * @return */ @ExceptionHandler({AuthenticationException.class}) public ResponseEntity authenException(Exception e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); } }

05

测试

我们使用PostMan 进行service的测试

  1. 密码正确的测试

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(6)

2 .密码不正确的测试

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(7)

  1. 用户不存在的测试

springboot权限验证的配置文件(2.SpringBoot与Shiro整合用户名密码校验)(8)

测试结果基本达到了我们的预期,但是你有没有觉得好像缺点什么呢,

是的没错,为什么我们的密码是明文存储的,这很不安全。

下一篇文章,让我们一起来聊一聊Shiro是如何帮我们完成加密和解密工作的。

代码经被上传至Gitee仓库,有需要者可私信笔者

,